Merge "Add getHdrOutputConversionSupport to SurfaceComposerClient."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index f54f132..cd8f3cd 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,58 +4,7 @@
       "name": "SurfaceFlinger_test",
       "options": [
         {
-          "include-filter": "*CredentialsTest.*"
-        },
-        {
-          "include-filter": "*SurfaceFlingerStress.*"
-        },
-        {
-          "include-filter": "*SurfaceInterceptorTest.*"
-        },
-        {
-          "include-filter": "*LayerTransactionTest.*"
-        },
-        {
-          "include-filter": "*LayerTypeTransactionTest.*"
-        },
-        {
-          "include-filter": "*LayerUpdateTest.*"
-        },
-        {
-          "include-filter": "*GeometryLatchingTest.*"
-        },
-        {
-          "include-filter": "*CropLatchingTest.*"
-        },
-        {
-          "include-filter": "*ChildLayerTest.*"
-        },
-        {
-          "include-filter": "*ScreenCaptureTest.*"
-        },
-        {
-          "include-filter": "*ScreenCaptureChildOnlyTest.*"
-        },
-        {
-          "include-filter": "*DereferenceSurfaceControlTest.*"
-        },
-        {
-          "include-filter": "*BoundlessLayerTest.*"
-        },
-        {
-          "include-filter": "*MultiDisplayLayerBoundsTest.*"
-        },
-        {
-          "include-filter": "*InvalidHandleTest.*"
-        },
-        {
-          "include-filter": "*VirtualDisplayTest.*"
-        },
-        {
-          "include-filter": "*RelativeZTest.*"
-        },
-        {
-          "include-filter": "*RefreshRateOverlayTest.*"
+          "include-filter": "*"
         },
         {
           "exclude-filter": "*ChildLayerTest#ChildrenSurviveParentDestruction"
@@ -76,58 +25,7 @@
       "name": "SurfaceFlinger_test",
       "options": [
         {
-          "include-filter": "*CredentialsTest.*"
-        },
-        {
-          "include-filter": "*SurfaceFlingerStress.*"
-        },
-        {
-          "include-filter": "*SurfaceInterceptorTest.*"
-        },
-        {
-          "include-filter": "*LayerTransactionTest.*"
-        },
-        {
-          "include-filter": "*LayerTypeTransactionTest.*"
-        },
-        {
-          "include-filter": "*LayerUpdateTest.*"
-        },
-        {
-          "include-filter": "*GeometryLatchingTest.*"
-        },
-        {
-          "include-filter": "*CropLatchingTest.*"
-        },
-        {
-          "include-filter": "*ChildLayerTest.*"
-        },
-        {
-          "include-filter": "*ScreenCaptureTest.*"
-        },
-        {
-          "include-filter": "*ScreenCaptureChildOnlyTest.*"
-        },
-        {
-          "include-filter": "*DereferenceSurfaceControlTest.*"
-        },
-        {
-          "include-filter": "*BoundlessLayerTest.*"
-        },
-        {
-          "include-filter": "*MultiDisplayLayerBoundsTest.*"
-        },
-        {
-          "include-filter": "*InvalidHandleTest.*"
-        },
-        {
-          "include-filter": "*VirtualDisplayTest.*"
-        },
-        {
-          "include-filter": "*RelativeZTest.*"
-        },
-        {
-          "include-filter": "*RefreshRateOverlayTest.*"
+          "include-filter": "*"
         }
       ]
     }
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index 42e9e0f..a7bc018 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -58,6 +58,13 @@
     exit(0);
 }
 
+[[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);
+    MYLOGD("Finished retrieving a bugreport. Exiting.\n");
+    exit(0);
+}
+
 [[noreturn]] static void signalErrorAndExit(sp<IDumpstateListener> listener, int error_code) {
     listener->onError(error_code);
     exit(0);
@@ -192,6 +199,41 @@
     return binder::Status::ok();
 }
 
+binder::Status DumpstateService::retrieveBugreport(
+    int32_t calling_uid, const std::string& calling_package,
+    android::base::unique_fd bugreport_fd,
+    const std::string& bugreport_file,
+    const sp<IDumpstateListener>& listener) {
+
+    ds_ = &(Dumpstate::GetInstance());
+    DumpstateInfo* ds_info = new DumpstateInfo();
+    ds_info->ds = ds_;
+    ds_info->calling_uid = calling_uid;
+    ds_info->calling_package = calling_package;
+    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.
+    android::base::unique_fd devnull_fd(
+        TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
+
+    options->Initialize(Dumpstate::BugreportMode::BUGREPORT_DEFAULT,
+                        0, bugreport_fd, devnull_fd, false);
+
+    if (bugreport_fd.get() == -1) {
+        MYLOGE("Invalid filedescriptor");
+        signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
+    }
+    ds_->SetOptions(std::move(options));
+    ds_->path_ = bugreport_file;
+    pthread_t thread;
+    status_t err = pthread_create(&thread, nullptr, dumpstate_thread_retrieve, ds_info);
+    if (err != 0) {
+        MYLOGE("Could not create a thread");
+        signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR);
+    }
+    return binder::Status::ok();
+}
+
 status_t DumpstateService::dump(int fd, const Vector<String16>&) {
     std::lock_guard<std::mutex> lock(lock_);
     if (ds_ == nullptr) {
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 997999c..dd73319 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -46,6 +46,13 @@
                                   int bugreport_flags, const sp<IDumpstateListener>& listener,
                                   bool is_screenshot_requested) override;
 
+    binder::Status retrieveBugreport(int32_t calling_uid,
+                                     const std::string& calling_package,
+                                     android::base::unique_fd bugreport_fd,
+                                     const std::string& bugreport_file,
+                                     const sp<IDumpstateListener>& listener)
+                                     override;
+
     binder::Status cancelBugreport(int32_t calling_uid,
                                    const std::string& calling_package) override;
 
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index d4323af..0dc8f5a 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -50,7 +50,10 @@
     const int BUGREPORT_MODE_DEFAULT = 6;
 
     // Use pre-dumped data.
-    const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1;
+    const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 0x1;
+
+    // Defer user consent.
+    const int BUGREPORT_FLAG_DEFER_CONSENT = 0x2;
 
     /**
      * Speculatively pre-dumps UI data for a bugreport request that might come later.
@@ -100,4 +103,22 @@
      * @param callingPackage package of the original application that requested the cancellation.
      */
     void cancelBugreport(int callingUid, @utf8InCpp String callingPackage);
+
+    /**
+     * Retrieves a previously generated bugreport.
+     *
+     * <p>The caller must have previously generated a bugreport using
+     * {@link #startBugreport} with the {@link BUGREPORT_FLAG_DEFER_CONSENT}
+     * flag set.
+     *
+     * @param callingUid UID of the original application that requested the report.
+     * @param callingPackage package of the original application that requested the report.
+     * @param bugreportFd the file to which the zipped bugreport should be written
+     * @param bugreportFile the path of the bugreport file
+     * @param listener callback for updates; optional
+     */
+    void retrieveBugreport(int callingUid, @utf8InCpp String callingPackage,
+                           FileDescriptor bugreportFd,
+                           @utf8InCpp String bugreportFile,
+                           IDumpstateListener listener);
 }
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
index 50c1624..e8891d3 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
@@ -50,6 +50,9 @@
     /* There is currently a bugreport running. The caller should try again later. */
     const int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5;
 
+    /* There is no bugreport to retrieve for the given caller. */
+    const int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = 6;
+
     /**
      * Called on an error condition with one of the error codes listed above.
      */
@@ -57,8 +60,10 @@
 
     /**
      * Called when taking bugreport finishes successfully.
+     *
+     * @param bugreportFile The location of the bugreport file
      */
-    oneway void onFinished();
+    oneway void onFinished(@utf8InCpp String bugreportFile);
 
     /**
      * Called when screenshot is taken.
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index d77b458..a48313a 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -186,6 +186,7 @@
 #define SYSTEM_TRACE_SNAPSHOT "/data/misc/perfetto-traces/bugreport/systrace.pftrace"
 #define CGROUPFS_DIR "/sys/fs/cgroup"
 #define SDK_EXT_INFO "/apex/com.android.sdkext/bin/derive_sdk"
+#define DROPBOX_DIR "/data/system/dropbox"
 
 // TODO(narayan): Since this information has to be kept in sync
 // with tombstoned, we should just put it in a common header.
@@ -526,6 +527,15 @@
     return strcmp(path + len - sizeof(stat) + 1, stat); /* .../stat? */
 }
 
+static bool skip_wtf_strictmode(const char *path) {
+    if (strstr(path, "_wtf")) {
+        return true;
+    } else if (strstr(path, "_strictmode")) {
+        return true;
+    }
+    return false;
+}
+
 static bool skip_none(const char* path __attribute__((unused))) {
     return false;
 }
@@ -1895,6 +1905,11 @@
     DumpIpTablesAsRoot();
     DumpDynamicPartitionInfo();
     ds.AddDir(OTA_METADATA_DIR, true);
+    if (!PropertiesHelper::IsUserBuild()) {
+        // Include dropbox entry files inside ZIP, but exclude
+        // noisy WTF and StrictMode entries
+        dump_files("", DROPBOX_DIR, skip_wtf_strictmode, _add_file_from_fd);
+    }
 
     // Capture any IPSec policies in play. No keys are exposed here.
     RunCommand("IP XFRM POLICY", {"ip", "xfrm", "policy"}, CommandOptions::WithTimeout(10).Build());
@@ -2825,6 +2840,7 @@
                                         const android::base::unique_fd& screenshot_fd_in,
                                         bool is_screenshot_requested) {
     this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA;
+    this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_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));
@@ -2907,10 +2923,64 @@
 
 Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& calling_package) {
     Dumpstate::RunStatus status = RunInternal(calling_uid, calling_package);
-    if (listener_ != nullptr) {
+    HandleRunStatus(status);
+    return status;
+}
+
+Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package) {
+    Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package);
+    HandleRunStatus(status);
+    return status;
+}
+
+Dumpstate::RunStatus  Dumpstate::RetrieveInternal(int32_t calling_uid,
+                                                  const std::string& calling_package) {
+  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 =
+      android::os::CopyFileToFd(path_, options_->bugreport_fd.get());
+  if (copy_succeeded) {
+    android::os::UnlinkAndLogOnError(path_);
+  }
+  return copy_succeeded ? Dumpstate::RunStatus::OK
+                        : Dumpstate::RunStatus::ERROR;
+}
+
+void Dumpstate::HandleRunStatus(Dumpstate::RunStatus status) {
+      if (listener_ != nullptr) {
         switch (status) {
             case Dumpstate::RunStatus::OK:
-                listener_->onFinished();
+                listener_->onFinished(path_.c_str());
                 break;
             case Dumpstate::RunStatus::HELP:
                 break;
@@ -2928,9 +2998,7 @@
                 break;
         }
     }
-    return status;
 }
-
 void Dumpstate::Cancel() {
     CleanupTmpFiles();
     android::os::UnlinkAndLogOnError(log_path_);
@@ -3181,7 +3249,7 @@
 
     // Share the final file with the caller if the user has consented or Shell is the caller.
     Dumpstate::RunStatus status = Dumpstate::RunStatus::OK;
-    if (CalledByApi()) {
+    if (CalledByApi() && !options_->is_consent_deferred) {
         status = CopyBugreportIfUserConsented(calling_uid);
         if (status != Dumpstate::RunStatus::OK &&
             status != Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT) {
@@ -3270,6 +3338,15 @@
         return;
     }
 
+    // Include the proto logging from WMShell.
+    RunCommand(
+        // Empty name because it's not intended to be classified as a bugreport section.
+        // Actual logging files can be found as "/data/misc/wmtrace/shell_log.winscope"
+        // in the bugreport.
+        "", {"dumpsys", "activity", "service", "SystemUIService",
+             "WMShell", "protolog", "save-for-bugreport"},
+        CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
+
     // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol.
     for (const auto& service : {"input_method", "window"}) {
         RunCommand(
@@ -3326,9 +3403,11 @@
 }
 
 void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) {
-    if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) {
-        // No need to get consent for shell triggered dumpstates, or not through
-        // bugreporting API (i.e. no fd to copy back).
+    if (multiuser_get_app_id(calling_uid) == AID_SHELL ||
+        !CalledByApi() || options_->is_consent_deferred) {
+        // 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.
         return;
     }
     consent_callback_ = new ConsentCallback();
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 9f894b5..8a31c31 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -207,7 +207,9 @@
     // The flags used to customize bugreport requests.
     enum BugreportFlag {
         BUGREPORT_USE_PREDUMPED_UI_DATA =
-          android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+          android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
+        BUGREPORT_FLAG_DEFER_CONSENT =
+          android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT
     };
 
     static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS;
@@ -353,6 +355,15 @@
      */
     RunStatus Run(int32_t calling_uid, const std::string& calling_package);
 
+    /*
+     * Entry point for retrieving a previous-generated bugreport.
+     *
+     * Initialize() dumpstate before calling this method.
+     */
+    RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package);
+
+
+
     RunStatus ParseCommandlineAndRun(int argc, char* argv[]);
 
     /* Deletes in-progress files */
@@ -396,6 +407,7 @@
         bool progress_updates_to_socket = false;
         bool do_screenshot = false;
         bool is_screenshot_copied = false;
+        bool is_consent_deferred = false;
         bool is_remote_mode = false;
         bool show_header_only = false;
         bool telephony_only = false;
@@ -548,6 +560,7 @@
 
   private:
     RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package);
+    RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package);
 
     RunStatus DumpstateDefaultAfterCritical();
     RunStatus dumpstate();
@@ -572,6 +585,8 @@
 
     RunStatus HandleUserConsentDenied();
 
+    void HandleRunStatus(RunStatus status);
+
     // Copies bugreport artifacts over to the caller's directories provided there is user consent or
     // called by Shell.
     RunStatus CopyBugreportIfUserConsented(int32_t calling_uid);
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index 12a7cff..a80da4e 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -8,7 +8,6 @@
     socket dumpstate stream 0660 shell log
     disabled
     oneshot
-    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
 
 # dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
 # it is finished.
@@ -17,11 +16,9 @@
     class main
     disabled
     oneshot
-    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
 
 # bugreportd starts dumpstate binder service and makes it wait for a listener to connect.
 service bugreportd /system/bin/dumpstate -w
     class main
     disabled
     oneshot
-    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index b091c8e..ccf64fe 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -160,7 +160,7 @@
         return binder::Status::ok();
     }
 
-    binder::Status onFinished() override {
+    binder::Status onFinished([[maybe_unused]] const std::string& bugreport_file) override {
         std::lock_guard<std::mutex> lock(lock_);
         is_finished_ = true;
         dprintf(out_fd_, "\rFinished");
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 1ffcafa..aa5219b 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -71,7 +71,7 @@
   public:
     MOCK_METHOD1(onProgress, binder::Status(int32_t progress));
     MOCK_METHOD1(onError, binder::Status(int32_t error_code));
-    MOCK_METHOD0(onFinished, binder::Status());
+    MOCK_METHOD1(onFinished, binder::Status(const std::string& bugreport_file));
     MOCK_METHOD1(onScreenshotTaken, binder::Status(bool success));
     MOCK_METHOD0(onUiIntensiveBugreportDumpsFinished, binder::Status());
 
@@ -486,6 +486,20 @@
     EXPECT_TRUE(options_.ValidateOptions());
 }
 
+TEST_F(DumpOptionsTest, InitializeBugreportFlags) {
+    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);
+    EXPECT_TRUE(options_.is_consent_deferred);
+    EXPECT_TRUE(options_.use_predumped_ui_data);
+
+    options_.Initialize(
+      Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true);
+    EXPECT_FALSE(options_.is_consent_deferred);
+    EXPECT_FALSE(options_.use_predumped_ui_data);
+}
+
 class DumpstateTest : public DumpstateBaseTest {
   public:
     void SetUp() {
@@ -1037,7 +1051,8 @@
 };
 
 // Generate a quick LimitedOnly report redirected to a file, open it and verify entry exist.
-TEST_F(ZippedBugReportStreamTest, StreamLimitedOnlyReport) {
+// TODO: broken test tracked in b/249983726
+TEST_F(ZippedBugReportStreamTest, DISABLED_StreamLimitedOnlyReport) {
     std::string out_path = kTestDataPath + "StreamLimitedOnlyReportOut.zip";
     android::base::unique_fd out_fd;
     CreateFd(out_path, &out_fd);
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index cc038ae..07809e2 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -39,6 +39,11 @@
 
 namespace android {
 
+bool is_multiuser_uid_isolated(uid_t uid) {
+    uid_t appid = multiuser_get_app_id(uid);
+    return appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;
+}
+
 #ifndef VENDORSERVICEMANAGER
 
 struct ManifestWithDescription {
@@ -222,6 +227,13 @@
 }
 #endif  // !VENDORSERVICEMANAGER
 
+ServiceManager::Service::~Service() {
+    if (!hasClients) {
+        // only expected to happen on process death
+        LOG(WARNING) << "a service was removed when there are clients";
+    }
+}
+
 ServiceManager::ServiceManager(std::unique_ptr<Access>&& access) : mAccess(std::move(access)) {
 // TODO(b/151696835): reenable performance hack when we solve bug, since with
 //     this hack and other fixes, it is unlikely we will see even an ephemeral
@@ -273,13 +285,8 @@
     if (auto it = mNameToService.find(name); it != mNameToService.end()) {
         service = &(it->second);
 
-        if (!service->allowIsolated) {
-            uid_t appid = multiuser_get_app_id(ctx.uid);
-            bool isIsolated = appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;
-
-            if (isIsolated) {
-                return nullptr;
-            }
+        if (!service->allowIsolated && is_multiuser_uid_isolated(ctx.uid)) {
+            return nullptr;
         }
         out = service->binder;
     }
@@ -293,8 +300,13 @@
     }
 
     if (out) {
-        // Setting this guarantee each time we hand out a binder ensures that the client-checking
-        // loop knows about the event even if the client immediately drops the service
+        // Force onClients to get sent, and then make sure the timerfd won't clear it
+        // by setting guaranteeClient again. This logic could be simplified by using
+        // a time-based guarantee. However, forcing onClients(true) to get sent
+        // right here is always going to be important for processes serving multiple
+        // lazy interfaces.
+        service->guaranteeClient = true;
+        CHECK(handleServiceClientCallback(2 /* sm + transaction */, name, false));
         service->guaranteeClient = true;
     }
 
@@ -384,8 +396,13 @@
     };
 
     if (auto it = mNameToRegistrationCallback.find(name); it != mNameToRegistrationCallback.end()) {
+        // See also getService - handles case where client never gets the service,
+        // we want the service to quit.
+        mNameToService[name].guaranteeClient = true;
+        CHECK(handleServiceClientCallback(2 /* sm + transaction */, name, false));
+        mNameToService[name].guaranteeClient = true;
+
         for (const sp<IServiceCallback>& cb : it->second) {
-            mNameToService[name].guaranteeClient = true;
             // permission checked in registerForNotifications
             cb->onRegistration(name, binder);
         }
@@ -425,7 +442,17 @@
     auto ctx = mAccess->getCallingContext();
 
     if (!mAccess->canFind(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY);
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux");
+    }
+
+    // note - we could allow isolated apps to get notifications if we
+    // keep track of isolated callbacks and non-isolated callbacks, but
+    // this is done since isolated apps shouldn't access lazy services
+    // so we should be able to use different APIs to keep things simple.
+    // Here, we disallow everything, because the service might not be
+    // registered yet.
+    if (is_multiuser_uid_isolated(ctx.uid)) {
+        return Status::fromExceptionCode(Status::EX_SECURITY, "isolated app");
     }
 
     if (!isValidServiceName(name)) {
@@ -696,67 +723,74 @@
 
 void ServiceManager::handleClientCallbacks() {
     for (const auto& [name, service] : mNameToService) {
-        handleServiceClientCallback(name, true);
+        handleServiceClientCallback(1 /* sm has one refcount */, name, true);
     }
 }
 
-ssize_t ServiceManager::handleServiceClientCallback(const std::string& serviceName,
-                                                    bool isCalledOnInterval) {
+bool ServiceManager::handleServiceClientCallback(size_t knownClients,
+                                                 const std::string& serviceName,
+                                                 bool isCalledOnInterval) {
     auto serviceIt = mNameToService.find(serviceName);
     if (serviceIt == mNameToService.end() || mNameToClientCallback.count(serviceName) < 1) {
-        return -1;
+        return true; // return we do have clients a.k.a. DON'T DO ANYTHING
     }
 
     Service& service = serviceIt->second;
     ssize_t count = service.getNodeStrongRefCount();
 
-    // binder driver doesn't support this feature
-    if (count == -1) return count;
+    // binder driver doesn't support this feature, consider we have clients
+    if (count == -1) return true;
 
-    bool hasClients = count > 1; // this process holds a strong count
+    bool hasKernelReportedClients = static_cast<size_t>(count) > knownClients;
 
     if (service.guaranteeClient) {
-        // we have no record of this client
-        if (!service.hasClients && !hasClients) {
-            sendClientCallbackNotifications(serviceName, true);
+        if (!service.hasClients && !hasKernelReportedClients) {
+            sendClientCallbackNotifications(serviceName, true,
+                                            "service is guaranteed to be in use");
         }
 
         // guarantee is temporary
         service.guaranteeClient = false;
     }
 
-    // only send notifications if this was called via the interval checking workflow
-    if (isCalledOnInterval) {
-        if (hasClients && !service.hasClients) {
-            // client was retrieved in some other way
-            sendClientCallbackNotifications(serviceName, true);
-        }
+    // Regardless of this situation, we want to give this notification as soon as possible.
+    // This way, we have a chance of preventing further thrashing.
+    if (hasKernelReportedClients && !service.hasClients) {
+        sendClientCallbackNotifications(serviceName, true, "we now have a record of a client");
+    }
 
-        // there are no more clients, but the callback has not been called yet
-        if (!hasClients && service.hasClients) {
-            sendClientCallbackNotifications(serviceName, false);
+    // But limit rate of shutting down service.
+    if (isCalledOnInterval) {
+        if (!hasKernelReportedClients && service.hasClients) {
+            sendClientCallbackNotifications(serviceName, false,
+                                            "we now have no record of a client");
         }
     }
 
-    return count;
+    // May be different than 'hasKernelReportedClients'. We intentionally delay
+    // information about clients going away to reduce thrashing.
+    return service.hasClients;
 }
 
-void ServiceManager::sendClientCallbackNotifications(const std::string& serviceName, bool hasClients) {
+void ServiceManager::sendClientCallbackNotifications(const std::string& serviceName,
+                                                     bool hasClients, const char* context) {
     auto serviceIt = mNameToService.find(serviceName);
     if (serviceIt == mNameToService.end()) {
-        ALOGW("sendClientCallbackNotifications could not find service %s", serviceName.c_str());
+        ALOGW("sendClientCallbackNotifications could not find service %s when %s",
+              serviceName.c_str(), context);
         return;
     }
     Service& service = serviceIt->second;
 
-    CHECK(hasClients != service.hasClients) << "Record shows: " << service.hasClients
-        << " so we can't tell clients again that we have client: " << hasClients;
+    CHECK_NE(hasClients, service.hasClients) << context;
 
-    ALOGI("Notifying %s they have clients: %d", serviceName.c_str(), hasClients);
+    ALOGI("Notifying %s they %s (previously: %s) have clients when %s", serviceName.c_str(),
+          hasClients ? "do" : "don't", service.hasClients ? "do" : "don't", context);
 
     auto ccIt = mNameToClientCallback.find(serviceName);
     CHECK(ccIt != mNameToClientCallback.end())
-        << "sendClientCallbackNotifications could not find callbacks for service ";
+            << "sendClientCallbackNotifications could not find callbacks for service when "
+            << context;
 
     for (const auto& callback : ccIt->second) {
         callback->onClients(service.binder, hasClients);
@@ -795,26 +829,29 @@
         return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
     }
 
+    // important because we don't have timer-based guarantees, we don't want to clear
+    // this
     if (serviceIt->second.guaranteeClient) {
         ALOGI("Tried to unregister %s, but there is about to be a client.", name.c_str());
         return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
     }
 
-    int clients = handleServiceClientCallback(name, false);
-
-    // clients < 0: feature not implemented or other error. Assume clients.
-    // Otherwise:
     // - kernel driver will hold onto one refcount (during this transaction)
     // - servicemanager has a refcount (guaranteed by this transaction)
-    // So, if clients > 2, then at least one other service on the system must hold a refcount.
-    if (clients < 0 || clients > 2) {
-        // client callbacks are either disabled or there are other clients
-        ALOGI("Tried to unregister %s, but there are clients: %d", name.c_str(), clients);
-        // Set this flag to ensure the clients are acknowledged in the next callback
+    constexpr size_t kKnownClients = 2;
+
+    if (handleServiceClientCallback(kKnownClients, name, false)) {
+        ALOGI("Tried to unregister %s, but there are clients.", name.c_str());
+
+        // Since we had a failed registration attempt, and the HIDL implementation of
+        // delaying service shutdown for multiple periods wasn't ported here... this may
+        // help reduce thrashing, but we should be able to remove it.
         serviceIt->second.guaranteeClient = true;
+
         return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
     }
 
+    ALOGI("Unregistering %s", name.c_str());
     mNameToService.erase(name);
 
     return Status::ok();
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index b24c11c..3aa6731 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -80,6 +80,8 @@
 
         // the number of clients of the service, including servicemanager itself
         ssize_t getNodeStrongRefCount();
+
+        ~Service();
     };
 
     using ServiceCallbackMap = std::map<std::string, std::vector<sp<IServiceCallback>>>;
@@ -91,9 +93,12 @@
     void removeRegistrationCallback(const wp<IBinder>& who,
                         ServiceCallbackMap::iterator* it,
                         bool* found);
-    ssize_t handleServiceClientCallback(const std::string& serviceName, bool isCalledOnInterval);
-     // Also updates mHasClients (of what the last callback was)
-    void sendClientCallbackNotifications(const std::string& serviceName, bool hasClients);
+    // returns whether there are known clients in addition to the count provided
+    bool handleServiceClientCallback(size_t knownClients, const std::string& serviceName,
+                                     bool isCalledOnInterval);
+    // Also updates mHasClients (of what the last callback was)
+    void sendClientCallbackNotifications(const std::string& serviceName, bool hasClients,
+                                         const char* context);
     // removes a callback from mNameToClientCallback, deleting the entry if the vector is empty
     // this updates the iterator to the next location
     void removeClientCallback(const wp<IBinder>& who, ClientCallbackMap::iterator* it);
diff --git a/cmds/servicemanager/servicemanager.rc b/cmds/servicemanager/servicemanager.rc
index 3bd6db5..4f92b3a 100644
--- a/cmds/servicemanager/servicemanager.rc
+++ b/cmds/servicemanager/servicemanager.rc
@@ -5,7 +5,7 @@
     critical
     file /dev/kmsg w
     onrestart setprop servicemanager.ready false
-    onrestart restart apexd
+    onrestart restart --only-if-running apexd
     onrestart restart audioserver
     onrestart restart gatekeeperd
     onrestart class_restart --only-enabled main
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index 0fd8d8e..cae32e3 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -383,6 +383,22 @@
 
     sp<CallbackHistorian> cb = sp<CallbackHistorian>::make();
 
+    EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), Status::EX_SECURITY);
+}
+
+TEST(GetService, IsolatedCantRegister) {
+    std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
+
+    EXPECT_CALL(*access, getCallingContext())
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }));
+    EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(true));
+
+    sp<ServiceManager> sm = sp<ServiceManager>::make(std::move(access));
+
+    sp<CallbackHistorian> cb = sp<CallbackHistorian>::make();
+
     EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(),
         Status::EX_SECURITY);
 }
diff --git a/data/etc/input/Android.bp b/data/etc/input/Android.bp
new file mode 100644
index 0000000..90f3c6b
--- /dev/null
+++ b/data/etc/input/Android.bp
@@ -0,0 +1,14 @@
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+filegroup {
+    name: "motion_predictor_model.fb",
+    srcs: ["motion_predictor_model.fb"],
+}
+
+prebuilt_etc {
+    name: "motion_predictor_model_prebuilt",
+    filename_from_src: true,
+    src: "motion_predictor_model.fb",
+}
diff --git a/data/etc/input/motion_predictor_model.fb b/data/etc/input/motion_predictor_model.fb
new file mode 100644
index 0000000..10b3c8b
--- /dev/null
+++ b/data/etc/input/motion_predictor_model.fb
Binary files differ
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 4a5bd5e..b494f89 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -37,6 +37,7 @@
 
 #include <android/api-level.h>
 #include <stdint.h>
+#include <unistd.h>
 
 __BEGIN_DECLS
 
@@ -173,7 +174,7 @@
  */
 int APerformanceHint_setThreads(
         APerformanceHintSession* session,
-        const int32_t* threadIds,
+        const pid_t* threadIds,
         size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
 
 __END_DECLS
diff --git a/include/input/Input.h b/include/input/Input.h
index 7573282..608519b 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -1113,6 +1113,7 @@
 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,
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 045e61b..68ebf75 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -16,9 +16,16 @@
 
 #pragma once
 
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
 #include <android-base/thread_annotations.h>
 #include <android/sysprop/InputProperties.sysprop.h>
 #include <input/Input.h>
+#include <input/TfLiteMotionPredictor.h>
 
 namespace android {
 
@@ -28,48 +35,52 @@
 
 /**
  * Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent
- * contains a set of samples in the future, up to "presentation time + offset".
+ * contains a set of samples in the future.
  *
  * The typical usage is like this:
  *
  * MotionPredictor predictor(offset = MY_OFFSET);
- * predictor.setExpectedPresentationTimeNanos(NEXT_PRESENT_TIME);
  * predictor.record(DOWN_MOTION_EVENT);
  * predictor.record(MOVE_MOTION_EVENT);
- * prediction = predictor.predict();
+ * prediction = predictor.predict(futureTime);
  *
- * The presentation time should be set some time before calling .predict(). It could be set before
- * or after the recorded motion events. Must be done on every frame.
- *
- * The resulting motion event will have eventTime <= (NEXT_PRESENT_TIME + MY_OFFSET). It might
- * contain historical data, which are additional samples from the latest recorded MotionEvent's
- * eventTime to the NEXT_PRESENT_TIME + MY_OFFSET.
+ * The resulting motion event will have eventTime <= (futureTime + MY_OFFSET). It might contain
+ * historical data, which are additional samples from the latest recorded MotionEvent's eventTime
+ * to the futureTime + MY_OFFSET.
  *
  * The offset is used to provide additional flexibility to the caller, in case the default present
  * time (typically provided by the choreographer) does not account for some delays, or to simply
- * reduce the aggressiveness of the prediction. Offset can be both positive and negative.
+ * reduce the aggressiveness of the prediction. Offset can be positive or negative.
  */
 class MotionPredictor {
 public:
     /**
      * Parameters:
      * predictionTimestampOffsetNanos: additional, constant shift to apply to the target
-     * presentation time. The prediction will target the time t=(presentationTime +
+     * prediction time. The prediction will target the time t=(prediction time +
      * predictionTimestampOffsetNanos).
      *
+     * modelPath: filesystem path to a TfLiteMotionPredictorModel flatbuffer, or nullptr to use the
+     * default model path.
+     *
      * checkEnableMotionPredition: the function to check whether the prediction should run. Used to
      * provide an additional way of turning prediction on and off. Can be toggled at runtime.
      */
-    MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+    MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath = nullptr,
                     std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
     void record(const MotionEvent& event);
     std::vector<std::unique_ptr<MotionEvent>> predict(nsecs_t timestamp);
     bool isPredictionAvailable(int32_t deviceId, int32_t source);
 
 private:
-    std::vector<MotionEvent> mEvents;
     const nsecs_t mPredictionTimestampOffsetNanos;
+    const std::string mModelPath;
     const std::function<bool()> mCheckMotionPredictionEnabled;
+
+    std::unique_ptr<TfLiteMotionPredictorModel> mModel;
+    // Buffers/events for each device seen by record().
+    std::unordered_map</*deviceId*/ int32_t, TfLiteMotionPredictorBuffers> mDeviceBuffers;
+    std::unordered_map</*deviceId*/ int32_t, MotionEvent> mLastEvents;
 };
 
 } // namespace android
diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h
new file mode 100644
index 0000000..67984b7
--- /dev/null
+++ b/include/input/RingBuffer.h
@@ -0,0 +1,293 @@
+/*
+ * 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 <algorithm>
+#include <compare>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+namespace android {
+
+// A fixed-size ring buffer of elements.
+//
+// Elements can only be removed from the front/back or added to the front/back, but with O(1)
+// performance. Elements from the opposing side are evicted when new elements are pushed onto a full
+// buffer.
+template <typename T>
+class RingBuffer {
+public:
+    using value_type = T;
+    using size_type = size_t;
+    using difference_type = ptrdiff_t;
+    using reference = value_type&;
+    using const_reference = const value_type&;
+    using pointer = value_type*;
+    using const_pointer = const value_type*;
+
+    template <typename U>
+    class Iterator;
+    using iterator = Iterator<T>;
+    using const_iterator = Iterator<const T>;
+
+    // Creates an empty ring buffer that can hold some capacity.
+    explicit RingBuffer(size_type capacity)
+          : mBuffer(std::allocator<value_type>().allocate(capacity)), mCapacity(capacity) {}
+
+    // Creates a full ring buffer holding a fixed number of elements initialised to some value.
+    explicit RingBuffer(size_type count, const_reference value) : RingBuffer(count) {
+        while (count) {
+            pushBack(value);
+            --count;
+        }
+    }
+
+    RingBuffer(const RingBuffer& other) : RingBuffer(other.capacity()) {
+        for (const auto& element : other) {
+            pushBack(element);
+        }
+    }
+
+    RingBuffer(RingBuffer&& other) noexcept { *this = std::move(other); }
+
+    ~RingBuffer() {
+        if (mBuffer) {
+            clear();
+            std::allocator<value_type>().deallocate(mBuffer, mCapacity);
+        }
+    }
+
+    RingBuffer& operator=(const RingBuffer& other) { return *this = RingBuffer(other); }
+
+    RingBuffer& operator=(RingBuffer&& other) noexcept {
+        if (this == &other) {
+            return *this;
+        }
+        if (mBuffer) {
+            clear();
+            std::allocator<value_type>().deallocate(mBuffer, mCapacity);
+        }
+        mBuffer = std::move(other.mBuffer);
+        mCapacity = other.mCapacity;
+        mBegin = other.mBegin;
+        mSize = other.mSize;
+        other.mBuffer = nullptr;
+        other.mCapacity = 0;
+        other.mBegin = 0;
+        other.mSize = 0;
+        return *this;
+    }
+
+    iterator begin() { return {*this, 0}; }
+    const_iterator begin() const { return {*this, 0}; }
+    iterator end() { return {*this, mSize}; }
+    const_iterator end() const { return {*this, mSize}; }
+
+    reference operator[](size_type i) { return mBuffer[bufferIndex(i)]; }
+    const_reference operator[](size_type i) const { return mBuffer[bufferIndex(i)]; }
+
+    // Removes all elements from the buffer.
+    void clear() {
+        std::destroy(begin(), end());
+        mSize = 0;
+    }
+
+    // Removes and returns the first element from the buffer.
+    value_type popFront() {
+        value_type element = mBuffer[mBegin];
+        std::destroy_at(std::addressof(mBuffer[mBegin]));
+        mBegin = next(mBegin);
+        --mSize;
+        return element;
+    }
+
+    // Removes and returns the last element from the buffer.
+    value_type popBack() {
+        size_type backIndex = bufferIndex(mSize - 1);
+        value_type element = mBuffer[backIndex];
+        std::destroy_at(std::addressof(mBuffer[backIndex]));
+        --mSize;
+        return element;
+    }
+
+    // Adds an element to the front of the buffer.
+    void pushFront(const value_type& element) { pushFront(value_type(element)); }
+    void pushFront(value_type&& element) {
+        mBegin = previous(mBegin);
+        if (size() == capacity()) {
+            mBuffer[mBegin] = std::forward<value_type>(element);
+        } else {
+            // The space at mBuffer[mBegin] is uninitialised.
+            // TODO: Use std::construct_at when it becomes available.
+            new (std::addressof(mBuffer[mBegin])) value_type(std::forward<value_type>(element));
+            ++mSize;
+        }
+    }
+
+    // Adds an element to the back of the buffer.
+    void pushBack(const value_type& element) { pushBack(value_type(element)); }
+    void pushBack(value_type&& element) {
+        if (size() == capacity()) {
+            mBuffer[mBegin] = std::forward<value_type>(element);
+            mBegin = next(mBegin);
+        } else {
+            // The space at mBuffer[...] is uninitialised.
+            // TODO: Use std::construct_at when it becomes available.
+            new (std::addressof(mBuffer[bufferIndex(mSize)]))
+                    value_type(std::forward<value_type>(element));
+            ++mSize;
+        }
+    }
+
+    bool empty() const { return mSize == 0; }
+    size_type capacity() const { return mCapacity; }
+    size_type size() const { return mSize; }
+
+    void swap(RingBuffer& other) noexcept {
+        using std::swap;
+        swap(mBuffer, other.mBuffer);
+        swap(mCapacity, other.mCapacity);
+        swap(mBegin, other.mBegin);
+        swap(mSize, other.mSize);
+    }
+
+    friend void swap(RingBuffer& lhs, RingBuffer& rhs) noexcept { lhs.swap(rhs); }
+
+    template <typename U>
+    class Iterator {
+    private:
+        using ContainerType = std::conditional_t<std::is_const_v<U>, const RingBuffer, RingBuffer>;
+
+    public:
+        using iterator_category = std::random_access_iterator_tag;
+        using size_type = ContainerType::size_type;
+        using difference_type = ContainerType::difference_type;
+        using value_type = std::remove_cv_t<U>;
+        using pointer = U*;
+        using reference = U&;
+
+        Iterator(ContainerType& container, size_type index)
+              : mContainer(container), mIndex(index) {}
+
+        Iterator(const Iterator&) = default;
+        Iterator& operator=(const Iterator&) = default;
+
+        Iterator& operator++() {
+            ++mIndex;
+            return *this;
+        }
+
+        Iterator operator++(int) {
+            Iterator iterator(*this);
+            ++(*this);
+            return iterator;
+        }
+
+        Iterator& operator--() {
+            --mIndex;
+            return *this;
+        }
+
+        Iterator operator--(int) {
+            Iterator iterator(*this);
+            --(*this);
+            return iterator;
+        }
+
+        Iterator& operator+=(difference_type n) {
+            mIndex += n;
+            return *this;
+        }
+
+        Iterator operator+(difference_type n) {
+            Iterator iterator(*this);
+            return iterator += n;
+        }
+
+        Iterator& operator-=(difference_type n) { return *this += -n; }
+
+        Iterator operator-(difference_type n) {
+            Iterator iterator(*this);
+            return iterator -= n;
+        }
+
+        difference_type operator-(const Iterator& other) { return mIndex - other.mIndex; }
+
+        bool operator==(const Iterator& rhs) const { return mIndex == rhs.mIndex; }
+
+        bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
+
+        friend auto operator<=>(const Iterator& lhs, const Iterator& rhs) {
+            return lhs.mIndex <=> rhs.mIndex;
+        }
+
+        reference operator[](difference_type n) { return *(*this + n); }
+
+        reference operator*() const { return mContainer[mIndex]; }
+        pointer operator->() const { return std::addressof(mContainer[mIndex]); }
+
+    private:
+        ContainerType& mContainer;
+        size_type mIndex = 0;
+    };
+
+private:
+    // Returns the index of the next element in mBuffer.
+    size_type next(size_type index) const {
+        if (index == capacity() - 1) {
+            return 0;
+        } else {
+            return index + 1;
+        }
+    }
+
+    // Returns the index of the previous element in mBuffer.
+    size_type previous(size_type index) const {
+        if (index == 0) {
+            return capacity() - 1;
+        } else {
+            return index - 1;
+        }
+    }
+
+    // Converts the index of an element in [0, size()] to its corresponding index in mBuffer.
+    size_type bufferIndex(size_type elementIndex) const {
+        CHECK_LE(elementIndex, size());
+        size_type index = mBegin + elementIndex;
+        if (index >= capacity()) {
+            index -= capacity();
+        }
+        CHECK_LT(index, capacity())
+                << android::base::StringPrintf("Invalid index calculated for element (%zu) "
+                                               "in buffer of size %zu",
+                                               elementIndex, size());
+        return index;
+    }
+
+    pointer mBuffer = nullptr;
+    size_type mCapacity = 0; // Total capacity of mBuffer.
+    size_type mBegin = 0;    // Index of the first initialised element in mBuffer.
+    size_type mSize = 0;     // Total number of initialised elements.
+};
+
+} // namespace android
diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h
new file mode 100644
index 0000000..54e2851
--- /dev/null
+++ b/include/input/TfLiteMotionPredictor.h
@@ -0,0 +1,150 @@
+/*
+ * 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 <array>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <span>
+
+#include <android-base/mapped_file.h>
+#include <input/RingBuffer.h>
+
+#include <tensorflow/lite/core/api/error_reporter.h>
+#include <tensorflow/lite/interpreter.h>
+#include <tensorflow/lite/model.h>
+#include <tensorflow/lite/signature_runner.h>
+
+namespace android {
+
+struct TfLiteMotionPredictorSample {
+    // The untransformed AMOTION_EVENT_AXIS_X and AMOTION_EVENT_AXIS_Y of the sample.
+    struct Point {
+        float x;
+        float y;
+    } position;
+    // The AMOTION_EVENT_AXIS_PRESSURE, _TILT, and _ORIENTATION.
+    float pressure;
+    float tilt;
+    float orientation;
+};
+
+inline TfLiteMotionPredictorSample::Point operator-(const TfLiteMotionPredictorSample::Point& lhs,
+                                                    const TfLiteMotionPredictorSample::Point& rhs) {
+    return {.x = lhs.x - rhs.x, .y = lhs.y - rhs.y};
+}
+
+class TfLiteMotionPredictorModel;
+
+// Buffer storage for a TfLiteMotionPredictorModel.
+class TfLiteMotionPredictorBuffers {
+public:
+    // Creates buffer storage for a model with the given input length.
+    TfLiteMotionPredictorBuffers(size_t inputLength);
+
+    // Adds a motion sample to the buffers.
+    void pushSample(int64_t timestamp, TfLiteMotionPredictorSample sample);
+
+    // Returns true if the buffers are complete enough to generate a prediction.
+    bool isReady() const {
+        // Predictions can't be applied unless there are at least two points to determine
+        // the direction to apply them in.
+        return mAxisFrom && mAxisTo;
+    }
+
+    // Resets all buffers to their initial state.
+    void reset();
+
+    // Copies the buffers to those of a model for prediction.
+    void copyTo(TfLiteMotionPredictorModel& model) const;
+
+    // Returns the current axis of the buffer's samples. Only valid if isReady().
+    TfLiteMotionPredictorSample axisFrom() const { return *mAxisFrom; }
+    TfLiteMotionPredictorSample axisTo() const { return *mAxisTo; }
+
+    // Returns the timestamp of the last sample.
+    int64_t lastTimestamp() const { return mTimestamp; }
+
+private:
+    int64_t mTimestamp = 0;
+
+    RingBuffer<float> mInputR;
+    RingBuffer<float> mInputPhi;
+    RingBuffer<float> mInputPressure;
+    RingBuffer<float> mInputTilt;
+    RingBuffer<float> mInputOrientation;
+
+    // The samples defining the current polar axis.
+    std::optional<TfLiteMotionPredictorSample> mAxisFrom;
+    std::optional<TfLiteMotionPredictorSample> mAxisTo;
+};
+
+// A TFLite model for generating motion predictions.
+class TfLiteMotionPredictorModel {
+public:
+    // Creates a model from an encoded Flatbuffer model.
+    static std::unique_ptr<TfLiteMotionPredictorModel> create(const char* modelPath);
+
+    ~TfLiteMotionPredictorModel();
+
+    // Returns the length of the model's input buffers.
+    size_t inputLength() const;
+
+    // Executes the model.
+    // Returns true if the model successfully executed and the output tensors can be read.
+    bool invoke();
+
+    // Returns mutable buffers to the input tensors of inputLength() elements.
+    std::span<float> inputR();
+    std::span<float> inputPhi();
+    std::span<float> inputPressure();
+    std::span<float> inputOrientation();
+    std::span<float> inputTilt();
+
+    // Returns immutable buffers to the output tensors of identical length. Only valid after a
+    // successful call to invoke().
+    std::span<const float> outputR() const;
+    std::span<const float> outputPhi() const;
+    std::span<const float> outputPressure() const;
+
+private:
+    explicit TfLiteMotionPredictorModel(std::unique_ptr<android::base::MappedFile> model);
+
+    void allocateTensors();
+    void attachInputTensors();
+    void attachOutputTensors();
+
+    TfLiteTensor* mInputR = nullptr;
+    TfLiteTensor* mInputPhi = nullptr;
+    TfLiteTensor* mInputPressure = nullptr;
+    TfLiteTensor* mInputTilt = nullptr;
+    TfLiteTensor* mInputOrientation = nullptr;
+
+    const TfLiteTensor* mOutputR = nullptr;
+    const TfLiteTensor* mOutputPhi = nullptr;
+    const TfLiteTensor* mOutputPressure = nullptr;
+
+    std::unique_ptr<android::base::MappedFile> mFlatBuffer;
+    std::unique_ptr<tflite::ErrorReporter> mErrorReporter;
+    std::unique_ptr<tflite::FlatBufferModel> mModel;
+    std::unique_ptr<tflite::Interpreter> mInterpreter;
+    tflite::SignatureRunner* mRunner = nullptr;
+};
+
+} // namespace android
diff --git a/include/private/surface_control_private.h b/include/private/surface_control_private.h
index 7e6c515..138926e 100644
--- a/include/private/surface_control_private.h
+++ b/include/private/surface_control_private.h
@@ -19,6 +19,8 @@
 
 #include <stdint.h>
 
+#include <android/choreographer.h>
+
 __BEGIN_DECLS
 
 struct ASurfaceControl;
@@ -56,6 +58,13 @@
                                        ASurfaceControl_SurfaceStatsListener func);
 
 /**
+ * Gets the attached AChoreographer instance from the given \c surfaceControl. If there is no
+ * choreographer associated with the surface control, then a new instance of choreographer is
+ * created. The new choreographer is associated with the current thread's Looper.
+ */
+AChoreographer* ASurfaceControl_getChoreographer(ASurfaceControl* surfaceControl);
+
+/**
  * Returns the timestamp of when the buffer was acquired for a specific frame with frame number
  * obtained from ASurfaceControlStats_getFrameNumber.
  */
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 7e7bba3..c4c8ffb 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -215,6 +215,8 @@
 
 cc_defaults {
     name: "trusty_mock_defaults",
+    vendor_available: true,
+    host_supported: true,
 
     header_libs: [
         "trusty_mock_headers",
@@ -287,6 +289,14 @@
     cflags: [
         "-DBINDER_WITH_KERNEL_IPC",
     ],
+    arch: {
+        // TODO(b/254713216): undefined symbol in BufferedTextOutput::getBuffer
+        riscv64: {
+            lto: {
+                thin: false,
+            },
+        },
+    },
 }
 
 cc_library {
@@ -508,6 +518,10 @@
             enabled: false,
         },
     },
+    visibility: [
+        ":__subpackages__",
+        "//system/tools/aidl:__subpackages__",
+    ],
 }
 
 // TODO(b/184872979): remove once the Rust API is created.
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index d03326e..53852d8 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -388,7 +388,8 @@
 {
     if (isRpcBinder()) {
         if (rpcSession()->getMaxIncomingThreads() < 1) {
-            ALOGE("Cannot register a DeathRecipient without any incoming connections.");
+            ALOGE("Cannot register a DeathRecipient without any incoming threads. Need to set max "
+                  "incoming threads to a value greater than 0 before calling linkToDeath.");
             return INVALID_OPERATION;
         }
     } else if constexpr (!kEnableKernelIpc) {
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 04cb61f..1488400 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -86,6 +86,12 @@
       "name": "binderRpcTest"
     },
     {
+      "name": "CtsRootRollbackManagerHostTestCases"
+    },
+    {
+      "name": "StagedRollbackTest"
+    },
+    {
       "name": "binderRpcTestNoKernel"
     },
     {
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index bad8cb1..471c994 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -64,6 +64,11 @@
     // For main functions - dangerous for libraries to use
     status_t setThreadPoolMaxThreadCount(size_t maxThreads);
     status_t enableOnewaySpamDetection(bool enable);
+
+    // Set the name of the current thread to look like a threadpool
+    // thread. Typically this is called before joinThreadPool.
+    //
+    // TODO: remove this API, and automatically set it intelligently.
     void giveThreadPoolName();
 
     String8 getDriverName();
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index 9b0d222..b0c7f6d 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -139,30 +139,6 @@
 };
 
 template <typename _T>
-class ToEmptyString {
-    template <typename _U>
-    static std::enable_if_t<false
-#ifdef HAS_NDK_INTERFACE
-                                    || std::is_base_of_v<::ndk::ICInterface, _U>
-#if __ANDROID_API__ >= 31
-                                    || std::is_same_v<::ndk::AParcelableHolder, _U>
-#endif
-#endif  // HAS_NDK_INTERFACE
-#ifdef HAS_CPP_INTERFACE
-                                    || std::is_base_of_v<IInterface, _U> ||
-                                    std::is_same_v<IBinder, _U>
-#endif
-                            ,
-                            std::true_type>
-    _test(int);
-    template <typename _U>
-    static std::false_type _test(...);
-
-   public:
-    enum { value = decltype(_test<_T>(0))::value };
-};
-
-template <typename _T>
 struct TypeDependentFalse {
     enum { value = false };
 };
@@ -171,9 +147,7 @@
 
 template <typename _T>
 std::string ToString(const _T& t) {
-    if constexpr (details::ToEmptyString<_T>::value) {
-        return "<unimplemented>";
-    } else if constexpr (std::is_same_v<bool, _T>) {
+    if constexpr (std::is_same_v<bool, _T>) {
         return t ? "true" : "false";
     } else if constexpr (std::is_same_v<char16_t, _T>) {
         // TODO(b/244494451): codecvt is deprecated in C++17 -- suppress the
@@ -193,6 +167,24 @@
         return ss.str();
     } else if constexpr (std::is_same_v<::ndk::ScopedFileDescriptor, _T>) {
         return "fd:" + std::to_string(t.get());
+    } else if constexpr (std::is_base_of_v<::ndk::ICInterface, _T>) {
+        // TODO(b/266248339): this format is to make it easy to handle resolv_integration_test
+        // freezing the output format. We would like to print more info.
+        return "<interface>";
+#if __ANDROID_API__ >= 31
+    } else if constexpr (std::is_same_v<::ndk::AParcelableHolder, _T>) {
+        return "AParcelableHolder";
+#endif
+#endif  // HAS_NDK_INTERFACE
+#ifdef HAS_CPP_INTERFACE
+    } else if constexpr (std::is_base_of_v<IInterface, _T>) {
+        std::stringstream ss;
+        ss << "interface:" << std::hex << &t;
+        return ss.str();
+    } else if constexpr (std::is_same_v<IBinder, _T>) {
+        std::stringstream ss;
+        ss << "binder:" << std::hex << &t;
+        return ss.str();
 #endif
 #ifdef HAS_STRING16
     } else if constexpr (std::is_same_v<String16, _T>) {
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index ad4188f..86d5ed2 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -38,6 +38,22 @@
         AIBinder* binder, const char* instance) __INTRODUCED_IN(29);
 
 /**
+ * This registers the service with the default service manager under this instance name. This does
+ * not take ownership of binder.
+ *
+ * WARNING: when using this API across an APEX boundary, do not use with unstable
+ * AIDL services. TODO(b/139325195)
+ *
+ * \param binder object to register globally with the service manager.
+ * \param instance identifier of the service. This will be used to lookup the service.
+ * \param allowIsolated allows if this service can be isolated.
+ *
+ * \return EX_NONE on success.
+ */
+__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithAllowIsolated(
+        AIBinder* binder, const char* instance, bool allowIsolated) __INTRODUCED_IN(34);
+
+/**
  * Gets a binder object with this specific instance name. Will return nullptr immediately if the
  * service is not available This also implicitly calls AIBinder_incStrong (so the caller of this
  * function is responsible for calling AIBinder_decStrong).
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 54e4628..5f2f617 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -163,6 +163,7 @@
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
+    AServiceManager_addServiceWithAllowIsolated;
     extern "C++" {
         AIBinder_fromPlatformBinder*;
         AIBinder_toPlatformBinder*;
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index e107c83..2763ddb 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -41,6 +41,19 @@
     status_t exception = sm->addService(String16(instance), binder->getBinder());
     return PruneException(exception);
 }
+
+binder_exception_t AServiceManager_addServiceWithAllowIsolated(AIBinder* binder,
+                                                               const char* instance,
+                                                               bool allowIsolated) {
+    if (binder == nullptr || instance == nullptr) {
+        return EX_ILLEGAL_ARGUMENT;
+    }
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated);
+    return PruneException(exception);
+}
+
 AIBinder* AServiceManager_checkService(const char* instance) {
     if (instance == nullptr) {
         return nullptr;
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index afb73e9..38dd4fe 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -23,7 +23,13 @@
         "liblibc",
         "liblog_rust",
     ],
+    visibility: [
+        "//device/google/cuttlefish/shared/minidroid/sample",
+        "//packages/modules/Uwb",
+        "//packages/modules/Virtualization:__subpackages__",
+    ],
     apex_available: [
+        "//apex_available:platform",
         "com.android.compos",
         "com.android.uwb",
         "com.android.virt",
@@ -51,6 +57,7 @@
         "libutils",
     ],
     apex_available: [
+        "//apex_available:platform",
         "com.android.compos",
         "com.android.uwb",
         "com.android.virt",
@@ -84,6 +91,7 @@
         "libutils",
     ],
     apex_available: [
+        "//apex_available:platform",
         "com.android.compos",
         "com.android.uwb",
         "com.android.virt",
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 7006f87..e609987 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -370,6 +370,31 @@
     ],
 }
 
+cc_binary {
+    name: "binderRpcTest_on_trusty_mock",
+    defaults: [
+        "trusty_mock_defaults",
+    ],
+
+    srcs: [
+        "binderRpcUniversalTests.cpp",
+        "binderRpcTestCommon.cpp",
+        "binderRpcTestTrusty.cpp",
+    ],
+
+    shared_libs: [
+        "libbinder_on_trusty_mock",
+        "libbase",
+        "libutils",
+        "libcutils",
+    ],
+
+    static_libs: [
+        "binderRpcTestIface-cpp",
+        "libgtest",
+    ],
+}
+
 cc_test {
     name: "binderRpcTest",
     defaults: [
@@ -382,6 +407,7 @@
     required: [
         "libbinder_on_trusty_mock",
         "binderRpcTestService_on_trusty_mock",
+        "binderRpcTest_on_trusty_mock",
     ],
 }
 
diff --git a/libs/binder/tests/binderRpcTestTrusty.cpp b/libs/binder/tests/binderRpcTestTrusty.cpp
new file mode 100644
index 0000000..b3bb5eb
--- /dev/null
+++ b/libs/binder/tests/binderRpcTestTrusty.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "binderRpcTest"
+
+#include <android-base/stringprintf.h>
+#include <binder/RpcTransportTipcTrusty.h>
+#include <trusty-gtest.h>
+#include <trusty_ipc.h>
+
+#include "binderRpcTestFixture.h"
+
+namespace android {
+
+// Destructors need to be defined, even if pure virtual
+ProcessSession::~ProcessSession() {}
+
+class TrustyProcessSession : public ProcessSession {
+public:
+    ~TrustyProcessSession() override {}
+
+    void setCustomExitStatusCheck(std::function<void(int wstatus)> /*f*/) override {
+        LOG_ALWAYS_FATAL("setCustomExitStatusCheck() not supported");
+    }
+
+    void terminate() override { LOG_ALWAYS_FATAL("terminate() not supported"); }
+};
+
+std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
+    auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
+    auto ret = PrintToString(type) + "_clientV" + std::to_string(clientVersion) + "_serverV" +
+            std::to_string(serverVersion);
+    if (singleThreaded) {
+        ret += "_single_threaded";
+    }
+    if (noKernel) {
+        ret += "_no_kernel";
+    }
+    return ret;
+}
+
+// This creates a new process serving an interface on a certain number of
+// threads.
+std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
+        const BinderRpcOptions& options) {
+    LOG_ALWAYS_FATAL_IF(options.numIncomingConnections != 0,
+                        "Non-zero incoming connections %zu on Trusty",
+                        options.numIncomingConnections);
+
+    uint32_t clientVersion = std::get<2>(GetParam());
+    uint32_t serverVersion = std::get<3>(GetParam());
+
+    auto ret = std::make_unique<TrustyProcessSession>();
+
+    status_t status;
+    for (size_t i = 0; i < options.numSessions; i++) {
+        auto factory = android::RpcTransportCtxFactoryTipcTrusty::make();
+        auto session = android::RpcSession::make(std::move(factory));
+
+        EXPECT_TRUE(session->setProtocolVersion(clientVersion));
+        session->setMaxOutgoingThreads(options.numOutgoingConnections);
+        session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
+
+        status = session->setupPreconnectedClient({}, [&]() {
+            auto port = trustyIpcPort(serverVersion);
+            int rc = connect(port.c_str(), IPC_CONNECT_WAIT_FOR_PORT);
+            LOG_ALWAYS_FATAL_IF(rc < 0, "Failed to connect to service: %d", rc);
+            return base::unique_fd(rc);
+        });
+        if (options.allowConnectFailure && status != OK) {
+            ret->sessions.clear();
+            break;
+        }
+        LOG_ALWAYS_FATAL_IF(status != OK, "Failed to connect to service: %s",
+                            statusToString(status).c_str());
+        ret->sessions.push_back({session, session->getRootObject()});
+    }
+
+    return ret;
+}
+
+INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc,
+                        ::testing::Combine(::testing::Values(SocketType::TIPC),
+                                           ::testing::Values(RpcSecurity::RAW),
+                                           ::testing::ValuesIn(testVersions()),
+                                           ::testing::ValuesIn(testVersions()),
+                                           ::testing::Values(false), ::testing::Values(true)),
+                        BinderRpc::PrintParamInfo);
+
+} // namespace android
+
+PORT_GTEST(BinderRpcTest, "com.android.trusty.binderRpcTest");
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index 2249e5c..11a22b0 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -386,11 +386,11 @@
     EXPECT_EQ(b, weak.promote());
 }
 
-#define expectSessions(expected, iface)                   \
+#define EXPECT_SESSIONS(expected, iface)                  \
     do {                                                  \
         int session;                                      \
         EXPECT_OK((iface)->getNumOpenSessions(&session)); \
-        EXPECT_EQ(expected, session);                     \
+        EXPECT_EQ(static_cast<int>(expected), session);   \
     } while (false)
 
 TEST_P(BinderRpc, SingleSession) {
@@ -402,9 +402,9 @@
     EXPECT_OK(session->getName(&out));
     EXPECT_EQ("aoeu", out);
 
-    expectSessions(1, proc.rootIface);
+    EXPECT_SESSIONS(1, proc.rootIface);
     session = nullptr;
-    expectSessions(0, proc.rootIface);
+    EXPECT_SESSIONS(0, proc.rootIface);
 }
 
 TEST_P(BinderRpc, ManySessions) {
@@ -413,24 +413,24 @@
     std::vector<sp<IBinderRpcSession>> sessions;
 
     for (size_t i = 0; i < 15; i++) {
-        expectSessions(i, proc.rootIface);
+        EXPECT_SESSIONS(i, proc.rootIface);
         sp<IBinderRpcSession> session;
         EXPECT_OK(proc.rootIface->openSession(std::to_string(i), &session));
         sessions.push_back(session);
     }
-    expectSessions(sessions.size(), proc.rootIface);
+    EXPECT_SESSIONS(sessions.size(), proc.rootIface);
     for (size_t i = 0; i < sessions.size(); i++) {
         std::string out;
         EXPECT_OK(sessions.at(i)->getName(&out));
         EXPECT_EQ(std::to_string(i), out);
     }
-    expectSessions(sessions.size(), proc.rootIface);
+    EXPECT_SESSIONS(sessions.size(), proc.rootIface);
 
     while (!sessions.empty()) {
         sessions.pop_back();
-        expectSessions(sessions.size(), proc.rootIface);
+        EXPECT_SESSIONS(sessions.size(), proc.rootIface);
     }
-    expectSessions(0, proc.rootIface);
+    EXPECT_SESSIONS(0, proc.rootIface);
 }
 
 TEST_P(BinderRpc, OnewayCallDoesNotWait) {
@@ -483,7 +483,7 @@
                     cb->mCv.wait_for(_l, 1s, [&] { return !cb->mValues.empty(); });
                 }
 
-                EXPECT_EQ(cb->mValues.size(), 1)
+                EXPECT_EQ(cb->mValues.size(), 1UL)
                         << "callIsOneway: " << callIsOneway
                         << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
                 if (cb->mValues.empty()) continue;
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 18ce316..109da75 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -117,7 +117,15 @@
         *ctx_p = channelContext;
     };
 
-    base::unique_fd clientFd(chan);
+    // We need to duplicate the channel handle here because the tipc library
+    // owns the original handle and closes is automatically on channel cleanup.
+    // We use dup() because Trusty does not have fcntl().
+    // NOLINTNEXTLINE(android-cloexec-dup)
+    handle_t chanDup = dup(chan);
+    if (chanDup < 0) {
+        return chanDup;
+    }
+    base::unique_fd clientFd(chanDup);
     android::RpcTransportFd transportFd(std::move(clientFd));
 
     std::array<uint8_t, RpcServer::kRpcAddressSize> addr;
diff --git a/libs/binder/trusty/binderRpcTest/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json
new file mode 100644
index 0000000..d8b080f
--- /dev/null
+++ b/libs/binder/trusty/binderRpcTest/manifest.json
@@ -0,0 +1,6 @@
+{
+    "uuid": "9dbe9fb8-60fd-4bdd-af86-03e95d7ad78b",
+    "app_name": "binderRpcTest",
+    "min_heap": 163840,
+    "min_stack": 16384
+}
diff --git a/libs/binder/trusty/binderRpcTest/rules.mk b/libs/binder/trusty/binderRpcTest/rules.mk
new file mode 100644
index 0000000..ae39492
--- /dev/null
+++ b/libs/binder/trusty/binderRpcTest/rules.mk
@@ -0,0 +1,35 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_TESTS_DIR := frameworks/native/libs/binder/tests
+
+MODULE := $(LOCAL_DIR)
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+MODULE_SRCS += \
+	$(LIBBINDER_TESTS_DIR)/binderRpcUniversalTests.cpp \
+	$(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
+	$(LIBBINDER_TESTS_DIR)/binderRpcTestTrusty.cpp \
+
+MODULE_LIBRARY_DEPS += \
+	$(LOCAL_DIR)/aidl \
+	frameworks/native/libs/binder/trusty \
+	frameworks/native/libs/binder/trusty/ndk \
+	trusty/user/base/lib/googletest \
+	trusty/user/base/lib/libstdc++-trusty \
+
+include make/trusted_app.mk
diff --git a/libs/binder/trusty/build-config-usertests b/libs/binder/trusty/build-config-usertests
new file mode 100644
index 0000000..d0a1fbc
--- /dev/null
+++ b/libs/binder/trusty/build-config-usertests
@@ -0,0 +1,19 @@
+# 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.
+
+# This file lists userspace tests
+
+[
+    porttest("com.android.trusty.binderRpcTest"),
+]
diff --git a/libs/binder/trusty/include_mock/lib/tipc/tipc.h b/libs/binder/trusty/include_mock/lib/tipc/tipc.h
index f295be4..ead9f9c 100644
--- a/libs/binder/trusty/include_mock/lib/tipc/tipc.h
+++ b/libs/binder/trusty/include_mock/lib/tipc/tipc.h
@@ -15,7 +15,9 @@
  */
 #pragma once
 
-__BEGIN_DECLS
+#if defined(__cplusplus)
+extern "C" {
+#endif
 
 struct tipc_hset;
 
@@ -26,4 +28,6 @@
     return 0;
 }
 
-__END_DECLS
+#if defined(__cplusplus)
+}
+#endif
diff --git a/libs/binder/trusty/include_mock/trusty-gtest.h b/libs/binder/trusty/include_mock/trusty-gtest.h
new file mode 100644
index 0000000..046b403
--- /dev/null
+++ b/libs/binder/trusty/include_mock/trusty-gtest.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#define PORT_GTEST(suite, port) \
+    int main(void) {            \
+        return 0;               \
+    }
diff --git a/libs/binder/trusty/include_mock/trusty_ipc.h b/libs/binder/trusty/include_mock/trusty_ipc.h
index 43ab84a..db044c2 100644
--- a/libs/binder/trusty/include_mock/trusty_ipc.h
+++ b/libs/binder/trusty/include_mock/trusty_ipc.h
@@ -27,6 +27,8 @@
 #define IPC_PORT_ALLOW_TA_CONNECT 0x1
 #define IPC_PORT_ALLOW_NS_CONNECT 0x2
 
+#define IPC_CONNECT_WAIT_FOR_PORT 0x1
+
 #define IPC_HANDLE_POLL_HUP 0x1
 #define IPC_HANDLE_POLL_MSG 0x2
 #define IPC_HANDLE_POLL_SEND_UNBLOCKED 0x4
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
index 2f5a7f4..1300121 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -14,4 +14,6 @@
 #
 
 TRUSTY_USER_TESTS += \
+	frameworks/native/libs/binder/trusty/binderRpcTest \
 	frameworks/native/libs/binder/trusty/binderRpcTest/service \
+
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 6c9c28a..21900a0 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -254,6 +254,10 @@
     lto: {
         thin: true,
     },
+
+    cflags: [
+        "-Wthread-safety",
+    ],
 }
 
 // Used by media codec services exclusively as a static lib for
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 60603ba..9d82c14 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -20,6 +20,7 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 //#define LOG_NDEBUG 0
 
+#include <cutils/atomic.h>
 #include <gui/BLASTBufferQueue.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/BufferQueueConsumer.h>
@@ -35,6 +36,7 @@
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
+#include <android-base/thread_annotations.h>
 #include <chrono>
 
 using namespace std::chrono_literals;
@@ -63,6 +65,10 @@
     ATRACE_FORMAT("%s - %s(f:%u,a:%u)" x, __FUNCTION__, mName.c_str(), mNumFrameAvailable, \
                   mNumAcquired, ##__VA_ARGS__)
 
+#define UNIQUE_LOCK_WITH_ASSERTION(mutex) \
+    std::unique_lock _lock{mutex};        \
+    base::ScopedLockAssertion assumeLocked(mutex);
+
 void BLASTBufferItemConsumer::onDisconnect() {
     Mutex::Autolock lock(mMutex);
     mPreviouslyConnected = mCurrentlyConnected;
@@ -152,11 +158,11 @@
                                                       GraphicBuffer::USAGE_HW_COMPOSER |
                                                               GraphicBuffer::USAGE_HW_TEXTURE,
                                                       1, false, this);
-    static int32_t id = 0;
-    mName = name + "#" + std::to_string(id);
-    auto consumerName = mName + "(BLAST Consumer)" + std::to_string(id);
-    mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(id);
-    id++;
+    static std::atomic<uint32_t> nextId = 0;
+    mProducerId = nextId++;
+    mName = name + "#" + std::to_string(mProducerId);
+    auto consumerName = mName + "(BLAST Consumer)" + std::to_string(mProducerId);
+    mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(mProducerId);
     mBufferItemConsumer->setName(String8(consumerName.c_str()));
     mBufferItemConsumer->setFrameAvailableListener(this);
 
@@ -207,7 +213,7 @@
                               int32_t format) {
     LOG_ALWAYS_FATAL_IF(surface == nullptr, "BLASTBufferQueue: mSurfaceControl must not be NULL");
 
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     if (mFormat != format) {
         mFormat = format;
         mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format));
@@ -277,7 +283,7 @@
                                                     const sp<Fence>& /*presentFence*/,
                                                     const std::vector<SurfaceControlStats>& stats) {
     {
-        std::unique_lock _lock{mMutex};
+        std::lock_guard _lock{mMutex};
         BBQ_TRACE();
         BQA_LOGV("transactionCommittedCallback");
         if (!mSurfaceControlsWithPendingCallback.empty()) {
@@ -325,7 +331,7 @@
 void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence>& /*presentFence*/,
                                            const std::vector<SurfaceControlStats>& stats) {
     {
-        std::unique_lock _lock{mMutex};
+        std::lock_guard _lock{mMutex};
         BBQ_TRACE();
         BQA_LOGV("transactionCallback");
 
@@ -406,9 +412,8 @@
 void BLASTBufferQueue::releaseBufferCallback(
         const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
         std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+    std::lock_guard _lock{mMutex};
     BBQ_TRACE();
-
-    std::unique_lock _lock{mMutex};
     releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount,
                                 false /* fakeRelease */);
 }
@@ -423,17 +428,15 @@
     // to the buffer queue. This will prevent higher latency when we are running
     // on a lower refresh rate than the max supported. We only do that for EGL
     // clients as others don't care about latency
-    const bool isEGL = [&] {
-        const auto it = mSubmitted.find(id);
-        return it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL;
-    }();
+    const auto it = mSubmitted.find(id);
+    const bool isEGL = it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL;
 
     if (currentMaxAcquiredBufferCount) {
         mCurrentMaxAcquiredBufferCount = *currentMaxAcquiredBufferCount;
     }
 
-    const auto numPendingBuffersToHold =
-            isEGL ? std::max(0u, mMaxAcquiredBuffers - mCurrentMaxAcquiredBufferCount) : 0;
+    const uint32_t numPendingBuffersToHold =
+            isEGL ? std::max(0, mMaxAcquiredBuffers - (int32_t)mCurrentMaxAcquiredBufferCount) : 0;
 
     auto rb = ReleasedBuffer{id, releaseFence};
     if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) {
@@ -485,11 +488,19 @@
 
 status_t BLASTBufferQueue::acquireNextBufferLocked(
         const std::optional<SurfaceComposerClient::Transaction*> transaction) {
-    // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
-    // include the extra buffer when checking if we can acquire the next buffer.
+    // Check if we have frames available and we have not acquired the maximum number of buffers.
+    // Even with this check, the consumer can fail to acquire an additional buffer if the consumer
+    // has already acquired (mMaxAcquiredBuffers + 1) and the new buffer is not droppable. In this
+    // case mBufferItemConsumer->acquireBuffer will return with NO_BUFFER_AVAILABLE.
     if (mNumFrameAvailable == 0) {
-        BQA_LOGV("Can't process next buffer. No available frames");
-        return NOT_ENOUGH_DATA;
+        BQA_LOGV("Can't acquire next buffer. No available frames");
+        return BufferQueue::NO_BUFFER_AVAILABLE;
+    }
+
+    if (mNumAcquired >= (mMaxAcquiredBuffers + 2)) {
+        BQA_LOGV("Can't acquire next buffer. Already acquired max frames %d max:%d + 2",
+                 mNumAcquired, mMaxAcquiredBuffers);
+        return BufferQueue::NO_BUFFER_AVAILABLE;
     }
 
     if (mSurfaceControl == nullptr) {
@@ -562,7 +573,8 @@
             std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
                       std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
     sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE;
-    t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, releaseBufferCallback);
+    t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId,
+                 releaseBufferCallback);
     t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
     t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
     t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
@@ -607,7 +619,7 @@
     }
 
     {
-        std::unique_lock _lock{mTimestampMutex};
+        std::lock_guard _lock{mTimestampMutex};
         auto dequeueTime = mDequeueTimestamps.find(buffer->getId());
         if (dequeueTime != mDequeueTimestamps.end()) {
             Parcel p;
@@ -662,11 +674,11 @@
 void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
     std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
     SurfaceComposerClient::Transaction* prevTransaction = nullptr;
-    bool waitForTransactionCallback = !mSyncedFrameNumbers.empty();
 
     {
-        std::unique_lock _lock{mMutex};
+        UNIQUE_LOCK_WITH_ASSERTION(mMutex);
         BBQ_TRACE();
+        bool waitForTransactionCallback = !mSyncedFrameNumbers.empty();
 
         const bool syncTransactionSet = mTransactionReadyCallback != nullptr;
         BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
@@ -696,6 +708,15 @@
                     // flush out the shadow queue
                     acquireAndReleaseBuffer();
                 }
+            } else {
+                // Make sure the frame available count is 0 before proceeding with a sync to ensure
+                // the correct frame is used for the sync. The only way mNumFrameAvailable would be
+                // greater than 0 is if we already ran out of buffers previously. This means we
+                // need to flush the buffers before proceeding with the sync.
+                while (mNumFrameAvailable > 0) {
+                    BQA_LOGD("waiting until no queued buffers");
+                    mCallbackCV.wait(_lock);
+                }
             }
         }
 
@@ -711,6 +732,11 @@
                  item.mFrameNumber, boolToString(syncTransactionSet));
 
         if (syncTransactionSet) {
+            // Add to mSyncedFrameNumbers before waiting in case any buffers are released
+            // while waiting for a free buffer. The release and commit callback will try to
+            // acquire buffers if there are any available, but we don't want it to acquire
+            // in the case where a sync transaction wants the buffer.
+            mSyncedFrameNumbers.emplace(item.mFrameNumber);
             // If there's no available buffer and we're in a sync transaction, we need to wait
             // instead of returning since we guarantee a buffer will be acquired for the sync.
             while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
@@ -723,7 +749,6 @@
             incStrong((void*)transactionCommittedCallbackThunk);
             mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk,
                                                               static_cast<void*>(this));
-            mSyncedFrameNumbers.emplace(item.mFrameNumber);
             if (mAcquireSingleBuffer) {
                 prevCallback = mTransactionReadyCallback;
                 prevTransaction = mSyncTransaction;
@@ -745,25 +770,24 @@
 }
 
 void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) {
-    std::unique_lock _lock{mTimestampMutex};
+    std::lock_guard _lock{mTimestampMutex};
     mDequeueTimestamps[bufferId] = systemTime();
 };
 
 void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) {
-    std::unique_lock _lock{mTimestampMutex};
+    std::lock_guard _lock{mTimestampMutex};
     mDequeueTimestamps.erase(bufferId);
 };
 
 void BLASTBufferQueue::syncNextTransaction(
         std::function<void(SurfaceComposerClient::Transaction*)> callback,
         bool acquireSingleBuffer) {
-    BBQ_TRACE();
-
     std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
     SurfaceComposerClient::Transaction* prevTransaction = nullptr;
 
     {
         std::lock_guard _lock{mMutex};
+        BBQ_TRACE();
         // We're about to overwrite the previous call so we should invoke that callback
         // immediately.
         if (mTransactionReadyCallback) {
@@ -829,8 +853,8 @@
 class BBQSurface : public Surface {
 private:
     std::mutex mMutex;
-    sp<BLASTBufferQueue> mBbq;
-    bool mDestroyed = false;
+    sp<BLASTBufferQueue> mBbq GUARDED_BY(mMutex);
+    bool mDestroyed GUARDED_BY(mMutex) = false;
 
 public:
     BBQSurface(const sp<IGraphicBufferProducer>& igbp, bool controlledByApp,
@@ -851,7 +875,7 @@
 
     status_t setFrameRate(float frameRate, int8_t compatibility,
                           int8_t changeFrameRateStrategy) override {
-        std::unique_lock _lock{mMutex};
+        std::lock_guard _lock{mMutex};
         if (mDestroyed) {
             return DEAD_OBJECT;
         }
@@ -864,7 +888,7 @@
 
     status_t setFrameTimelineInfo(uint64_t frameNumber,
                                   const FrameTimelineInfo& frameTimelineInfo) override {
-        std::unique_lock _lock{mMutex};
+        std::lock_guard _lock{mMutex};
         if (mDestroyed) {
             return DEAD_OBJECT;
         }
@@ -874,7 +898,7 @@
     void destroy() override {
         Surface::destroy();
 
-        std::unique_lock _lock{mMutex};
+        std::lock_guard _lock{mMutex};
         mDestroyed = true;
         mBbq = nullptr;
     }
@@ -884,7 +908,7 @@
 // no timing issues.
 status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility,
                                         bool shouldBeSeamless) {
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     SurfaceComposerClient::Transaction t;
 
     return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply();
@@ -894,20 +918,20 @@
                                                 const FrameTimelineInfo& frameTimelineInfo) {
     ATRACE_FORMAT("%s(%s) frameNumber: %" PRIu64 " vsyncId: %" PRId64, __func__, mName.c_str(),
                   frameNumber, frameTimelineInfo.vsyncId);
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     mPendingFrameTimelines.push({frameNumber, frameTimelineInfo});
     return OK;
 }
 
 void BLASTBufferQueue::setSidebandStream(const sp<NativeHandle>& stream) {
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     SurfaceComposerClient::Transaction t;
 
     t.setSidebandStream(mSurfaceControl, stream).apply();
 }
 
 sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     sp<IBinder> scHandle = nullptr;
     if (includeSurfaceControlHandle && mSurfaceControl) {
         scHandle = mSurfaceControl->getHandle();
@@ -1098,6 +1122,7 @@
 }
 
 uint32_t BLASTBufferQueue::getLastTransformHint() const {
+    std::lock_guard _lock{mMutex};
     if (mSurfaceControl != nullptr) {
         return mSurfaceControl->getTransformHint();
     } else {
@@ -1106,18 +1131,18 @@
 }
 
 uint64_t BLASTBufferQueue::getLastAcquiredFrameNum() {
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     return mLastAcquiredFrameNumber;
 }
 
 bool BLASTBufferQueue::isSameSurfaceControl(const sp<SurfaceControl>& surfaceControl) const {
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl);
 }
 
 void BLASTBufferQueue::setTransactionHangCallback(
         std::function<void(const std::string&)> callback) {
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     mTransactionHangCallback = callback;
 }
 
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 6b25b26..99bf6ba 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -101,8 +101,9 @@
     return gChoreographer;
 }
 
-Choreographer::Choreographer(const sp<Looper>& looper)
-      : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp),
+Choreographer::Choreographer(const sp<Looper>& looper, const sp<IBinder>& layerHandle)
+      : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, {},
+                               layerHandle),
         mLooper(looper),
         mThreadId(std::this_thread::get_id()) {
     std::lock_guard<std::mutex> _l(gChoreographers.lock);
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 501e69a..8a88377 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -37,9 +37,10 @@
 
 DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
                                                gui::ISurfaceComposer::VsyncSource vsyncSource,
-                                               EventRegistrationFlags eventRegistration)
+                                               EventRegistrationFlags eventRegistration,
+                                               const sp<IBinder>& layerHandle)
       : mLooper(looper),
-        mReceiver(vsyncSource, eventRegistration),
+        mReceiver(vsyncSource, eventRegistration, layerHandle),
         mWaitingForVsync(false),
         mLastVsyncCount(0),
         mLastScheduleVsyncTime(0) {
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
index c52fb6b..6849a95 100644
--- a/libs/gui/DisplayEventReceiver.cpp
+++ b/libs/gui/DisplayEventReceiver.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "DisplayEventReceiver"
+
 #include <string.h>
 
 #include <utils/Errors.h>
@@ -32,7 +34,8 @@
 // ---------------------------------------------------------------------------
 
 DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource,
-                                           EventRegistrationFlags eventRegistration) {
+                                           EventRegistrationFlags eventRegistration,
+                                           const sp<IBinder>& layerHandle) {
     sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
     if (sf != nullptr) {
         mEventConnection = nullptr;
@@ -41,8 +44,8 @@
                                                  static_cast<
                                                          gui::ISurfaceComposer::EventRegistration>(
                                                          eventRegistration.get()),
-                                                 &mEventConnection);
-        if (mEventConnection != nullptr) {
+                                                 layerHandle, &mEventConnection);
+        if (status.isOk() && mEventConnection != nullptr) {
             mDataChannel = std::make_unique<gui::BitTube>();
             status = mEventConnection->stealReceiveChannel(mDataChannel.get());
             if (!status.isOk()) {
@@ -51,6 +54,8 @@
                 mDataChannel.reset();
                 mEventConnection.clear();
             }
+        } else {
+            ALOGE("DisplayEventConnection creation failed: status=%s", status.toString8().c_str());
         }
     }
 }
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index cefb9a7..a77ca04 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -63,8 +63,7 @@
                                  Vector<ComposerState>& state, const Vector<DisplayState>& displays,
                                  uint32_t flags, const sp<IBinder>& applyToken,
                                  const InputWindowCommands& commands, int64_t desiredPresentTime,
-                                 bool isAutoTimestamp,
-                                 const std::vector<client_cache_t>& uncacheBuffers,
+                                 bool isAutoTimestamp, const client_cache_t& uncacheBuffer,
                                  bool hasListenerCallbacks,
                                  const std::vector<ListenerCallbacks>& listenerCallbacks,
                                  uint64_t transactionId) override {
@@ -88,11 +87,8 @@
         SAFE_PARCEL(commands.write, data);
         SAFE_PARCEL(data.writeInt64, desiredPresentTime);
         SAFE_PARCEL(data.writeBool, isAutoTimestamp);
-        SAFE_PARCEL(data.writeUint32, static_cast<uint32_t>(uncacheBuffers.size()));
-        for (const client_cache_t& uncacheBuffer : uncacheBuffers) {
-            SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote());
-            SAFE_PARCEL(data.writeUint64, uncacheBuffer.id);
-        }
+        SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote());
+        SAFE_PARCEL(data.writeUint64, uncacheBuffer.id);
         SAFE_PARCEL(data.writeBool, hasListenerCallbacks);
 
         SAFE_PARCEL(data.writeVectorSize, listenerCallbacks);
@@ -162,14 +158,11 @@
             SAFE_PARCEL(data.readInt64, &desiredPresentTime);
             SAFE_PARCEL(data.readBool, &isAutoTimestamp);
 
-            SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize());
-            std::vector<client_cache_t> uncacheBuffers(count);
+            client_cache_t uncachedBuffer;
             sp<IBinder> tmpBinder;
-            for (size_t i = 0; i < count; i++) {
-                SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder);
-                uncacheBuffers[i].token = tmpBinder;
-                SAFE_PARCEL(data.readUint64, &uncacheBuffers[i].id);
-            }
+            SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder);
+            uncachedBuffer.token = tmpBinder;
+            SAFE_PARCEL(data.readUint64, &uncachedBuffer.id);
 
             bool hasListenerCallbacks = false;
             SAFE_PARCEL(data.readBool, &hasListenerCallbacks);
@@ -189,7 +182,7 @@
 
             return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken,
                                        inputWindowCommands, desiredPresentTime, isAutoTimestamp,
-                                       uncacheBuffers, hasListenerCallbacks, listenerCallbacks,
+                                       uncachedBuffer, hasListenerCallbacks, listenerCallbacks,
                                        transactionId);
         }
         default: {
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 985c549..ffe79a3 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -39,6 +39,12 @@
 
 } // Anonymous namespace
 
+namespace { // Anonymous
+
+constexpr int32_t kSerializedCallbackTypeOnCompelteWithJankData = 2;
+
+} // Anonymous namespace
+
 status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
     status_t err = output->writeUint64(frameNumber);
     if (err != NO_ERROR) return err;
@@ -349,7 +355,11 @@
 
 status_t CallbackId::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeInt64, id);
-    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
+    if (type == Type::ON_COMPLETE && includeJankData) {
+        SAFE_PARCEL(output->writeInt32, kSerializedCallbackTypeOnCompelteWithJankData);
+    } else {
+        SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
+    }
     return NO_ERROR;
 }
 
@@ -357,7 +367,13 @@
     SAFE_PARCEL(input->readInt64, &id);
     int32_t typeAsInt;
     SAFE_PARCEL(input->readInt32, &typeAsInt);
-    type = static_cast<CallbackId::Type>(typeAsInt);
+    if (typeAsInt == kSerializedCallbackTypeOnCompelteWithJankData) {
+        type = Type::ON_COMPLETE;
+        includeJankData = true;
+    } else {
+        type = static_cast<CallbackId::Type>(typeAsInt);
+        includeJankData = false;
+    }
     return NO_ERROR;
 }
 
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 43acb16..a6276e5 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -187,6 +187,8 @@
     }
     SAFE_PARCEL(output.writeParcelable, trustedPresentationThresholds);
     SAFE_PARCEL(output.writeParcelable, trustedPresentationListener);
+    SAFE_PARCEL(output.writeFloat, currentSdrHdrRatio);
+    SAFE_PARCEL(output.writeFloat, desiredSdrHdrRatio);
     return NO_ERROR;
 }
 
@@ -321,6 +323,11 @@
     SAFE_PARCEL(input.readParcelable, &trustedPresentationThresholds);
     SAFE_PARCEL(input.readParcelable, &trustedPresentationListener);
 
+    SAFE_PARCEL(input.readFloat, &tmpFloat);
+    currentSdrHdrRatio = tmpFloat;
+    SAFE_PARCEL(input.readFloat, &tmpFloat);
+    desiredSdrHdrRatio = tmpFloat;
+
     return NO_ERROR;
 }
 
@@ -568,6 +575,11 @@
         what |= eDataspaceChanged;
         dataspace = other.dataspace;
     }
+    if (other.what & eExtendedRangeBrightnessChanged) {
+        what |= eExtendedRangeBrightnessChanged;
+        desiredSdrHdrRatio = other.desiredSdrHdrRatio;
+        currentSdrHdrRatio = other.currentSdrHdrRatio;
+    }
     if (other.what & eHdrMetadataChanged) {
         what |= eHdrMetadataChanged;
         hdrMetadata = other.hdrMetadata;
@@ -672,6 +684,9 @@
         what |= eDimmingEnabledChanged;
         dimmingEnabled = other.dimmingEnabled;
     }
+    if (other.what & eFlushJankData) {
+        what |= eFlushJankData;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
@@ -714,6 +729,8 @@
     CHECK_DIFF(diff, eCropChanged, other, crop);
     if (other.what & eBufferChanged) diff |= eBufferChanged;
     CHECK_DIFF(diff, eDataspaceChanged, other, dataspace);
+    CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentSdrHdrRatio,
+                desiredSdrHdrRatio);
     CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata);
     if (other.what & eSurfaceDamageRegionChanged &&
         (!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) {
@@ -967,6 +984,7 @@
     SAFE_PARCEL(output->writeUint64, cachedBuffer.id);
     SAFE_PARCEL(output->writeBool, hasBarrier);
     SAFE_PARCEL(output->writeUint64, barrierFrameNumber);
+    SAFE_PARCEL(output->writeUint32, producerId);
 
     return NO_ERROR;
 }
@@ -1005,6 +1023,7 @@
 
     SAFE_PARCEL(input->readBool, &hasBarrier);
     SAFE_PARCEL(input->readUint64, &barrierFrameNumber);
+    SAFE_PARCEL(input->readUint32, &producerId);
 
     return NO_ERROR;
 }
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 47339fe..f51c402 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -53,6 +53,7 @@
 #include <ui/DisplayState.h>
 #include <ui/DynamicDisplayInfo.h>
 
+#include <android-base/thread_annotations.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
@@ -81,6 +82,8 @@
 int64_t generateId() {
     return (((int64_t)getpid()) << 32) | ++idCounter;
 }
+
+void emptyCallback(nsecs_t, const sp<Fence>&, const std::vector<SurfaceControlStats>&) {}
 } // namespace
 
 ComposerService::ComposerService()
@@ -248,6 +251,14 @@
                 surfaceControls,
         CallbackId::Type callbackType) {
     std::lock_guard<std::mutex> lock(mMutex);
+    return addCallbackFunctionLocked(callbackFunction, surfaceControls, callbackType);
+}
+
+CallbackId TransactionCompletedListener::addCallbackFunctionLocked(
+        const TransactionCompletedCallback& callbackFunction,
+        const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
+                surfaceControls,
+        CallbackId::Type callbackType) {
     startListeningLocked();
 
     CallbackId callbackId(getNextIdLocked(), callbackType);
@@ -256,6 +267,11 @@
 
     for (const auto& surfaceControl : surfaceControls) {
         callbackSurfaceControls[surfaceControl->getHandle()] = surfaceControl;
+
+        if (callbackType == CallbackId::Type::ON_COMPLETE &&
+            mJankListeners.count(surfaceControl->getLayerId()) != 0) {
+            callbackId.includeJankData = true;
+        }
     }
 
     return callbackId;
@@ -304,15 +320,26 @@
 }
 
 void TransactionCompletedListener::addSurfaceControlToCallbacks(
-        const sp<SurfaceControl>& surfaceControl,
-        const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds) {
+        SurfaceComposerClient::CallbackInfo& callbackInfo,
+        const sp<SurfaceControl>& surfaceControl) {
     std::lock_guard<std::mutex> lock(mMutex);
 
-    for (auto callbackId : callbackIds) {
+    bool includingJankData = false;
+    for (auto callbackId : callbackInfo.callbackIds) {
         mCallbacks[callbackId].surfaceControls.emplace(std::piecewise_construct,
                                                        std::forward_as_tuple(
                                                                surfaceControl->getHandle()),
                                                        std::forward_as_tuple(surfaceControl));
+        includingJankData = includingJankData || callbackId.includeJankData;
+    }
+
+    // If no registered callback is requesting jank data, but there is a jank listener registered
+    // on the new surface control, add a synthetic callback that requests the jank data.
+    if (!includingJankData && mJankListeners.count(surfaceControl->getLayerId()) != 0) {
+        CallbackId callbackId =
+                addCallbackFunctionLocked(&emptyCallback, callbackInfo.surfaceControls,
+                                          CallbackId::Type::ON_COMPLETE);
+        callbackInfo.callbackIds.emplace(callbackId);
     }
 }
 
@@ -606,13 +633,11 @@
         return NO_ERROR;
     }
 
-    uint64_t cache(const sp<GraphicBuffer>& buffer,
-                   std::optional<client_cache_t>& outUncacheBuffer) {
+    uint64_t cache(const sp<GraphicBuffer>& buffer) {
         std::lock_guard<std::mutex> lock(mMutex);
 
         if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) {
-            outUncacheBuffer = findLeastRecentlyUsedBuffer();
-            mBuffers.erase(outUncacheBuffer->id);
+            evictLeastRecentlyUsedBuffer();
         }
 
         buffer->addDeathCallback(removeDeadBufferCallback, nullptr);
@@ -623,13 +648,16 @@
 
     void uncache(uint64_t cacheId) {
         std::lock_guard<std::mutex> lock(mMutex);
-        if (mBuffers.erase(cacheId)) {
-            SurfaceComposerClient::doUncacheBufferTransaction(cacheId);
-        }
+        uncacheLocked(cacheId);
+    }
+
+    void uncacheLocked(uint64_t cacheId) REQUIRES(mMutex) {
+        mBuffers.erase(cacheId);
+        SurfaceComposerClient::doUncacheBufferTransaction(cacheId);
     }
 
 private:
-    client_cache_t findLeastRecentlyUsedBuffer() REQUIRES(mMutex) {
+    void evictLeastRecentlyUsedBuffer() REQUIRES(mMutex) {
         auto itr = mBuffers.begin();
         uint64_t minCounter = itr->second;
         auto minBuffer = itr;
@@ -643,8 +671,7 @@
             }
             itr++;
         }
-
-        return {.token = getToken(), .id = minBuffer->first};
+        uncacheLocked(minBuffer->first);
     }
 
     uint64_t getCounter() REQUIRES(mMutex) {
@@ -782,18 +809,6 @@
     InputWindowCommands inputWindowCommands;
     inputWindowCommands.read(*parcel);
 
-    count = static_cast<size_t>(parcel->readUint32());
-    if (count > parcel->dataSize()) {
-        return BAD_VALUE;
-    }
-    std::vector<client_cache_t> uncacheBuffers(count);
-    for (size_t i = 0; i < count; i++) {
-        sp<IBinder> tmpBinder;
-        SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder);
-        uncacheBuffers[i].token = tmpBinder;
-        SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id);
-    }
-
     // Parsing was successful. Update the object.
     mId = transactionId;
     mTransactionNestCount = transactionNestCount;
@@ -808,7 +823,6 @@
     mComposerStates = composerStates;
     mInputWindowCommands = inputWindowCommands;
     mApplyToken = applyToken;
-    mUncacheBuffers = std::move(uncacheBuffers);
     return NO_ERROR;
 }
 
@@ -860,13 +874,6 @@
     }
 
     mInputWindowCommands.write(*parcel);
-
-    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mUncacheBuffers.size()));
-    for (const client_cache_t& uncacheBuffer : mUncacheBuffers) {
-        SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote());
-        SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id);
-    }
-
     return NO_ERROR;
 }
 
@@ -929,15 +936,10 @@
         // register all surface controls for all callbackIds for this listener that is merging
         for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) {
             TransactionCompletedListener::getInstance()
-                    ->addSurfaceControlToCallbacks(surfaceControl,
-                                                   currentProcessCallbackInfo.callbackIds);
+                    ->addSurfaceControlToCallbacks(currentProcessCallbackInfo, surfaceControl);
         }
     }
 
-    for (const auto& cacheId : other.mUncacheBuffers) {
-        mUncacheBuffers.push_back(cacheId);
-    }
-
     mInputWindowCommands.merge(other.mInputWindowCommands);
 
     mMayContainBuffer |= other.mMayContainBuffer;
@@ -956,7 +958,6 @@
     mDisplayStates.clear();
     mListenerCallbacks.clear();
     mInputWindowCommands.clear();
-    mUncacheBuffers.clear();
     mMayContainBuffer = false;
     mTransactionNestCount = 0;
     mAnimation = false;
@@ -979,10 +980,10 @@
     uncacheBuffer.token = BufferCache::getInstance().getToken();
     uncacheBuffer.id = cacheId;
     Vector<ComposerState> composerStates;
-    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
-                                              ISurfaceComposer::eOneWay,
-                                              Transaction::getDefaultApplyToken(), {}, systemTime(),
-                                              true, {uncacheBuffer}, false, {}, generateId());
+    status_t status =
+            sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+                                    ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(),
+                                    {}, systemTime(), true, uncacheBuffer, false, {}, generateId());
     if (status != NO_ERROR) {
         ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s",
                         strerror(-status));
@@ -1020,11 +1021,7 @@
             s->bufferData->buffer = nullptr;
         } else {
             // Cache-miss. Include the buffer and send the new cacheId.
-            std::optional<client_cache_t> uncacheBuffer;
-            cacheId = BufferCache::getInstance().cache(s->bufferData->buffer, uncacheBuffer);
-            if (uncacheBuffer) {
-                mUncacheBuffers.push_back(*uncacheBuffer);
-            }
+            cacheId = BufferCache::getInstance().cache(s->bufferData->buffer);
         }
         s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged;
         s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken();
@@ -1157,7 +1154,8 @@
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
     sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
                             mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
-                            mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId);
+                            {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/,
+                            hasListenerCallbacks, listenerCallbacks, mId);
     mId = generateId();
 
     // Clear the current states and flags
@@ -1180,14 +1178,29 @@
 void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
     sApplyToken = applyToken;
 }
+
+status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(
+        const sp<SurfaceControl>& sc) {
+    Transaction t;
+    layer_state_t* s = t.getLayerState(sc);
+    if (!s) {
+        return BAD_INDEX;
+    }
+
+    s->what |= layer_state_t::eFlushJankData;
+    t.registerSurfaceControlForCallback(sc);
+    return t.apply(/*sync=*/false, /* oneWay=*/true);
+}
 // ---------------------------------------------------------------------------
 
-sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure) {
+sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure,
+                                                 float requestedRefereshRate) {
     sp<IBinder> display = nullptr;
     binder::Status status =
             ComposerServiceAIDL::getComposerService()->createDisplay(std::string(
                                                                              displayName.string()),
-                                                                     secure, &display);
+                                                                     secure, requestedRefereshRate,
+                                                                     &display);
     return status.isOk() ? display : nullptr;
 }
 
@@ -1251,8 +1264,7 @@
     auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()];
     callbackInfo.surfaceControls.insert(sc);
 
-    TransactionCompletedListener::getInstance()
-            ->addSurfaceControlToCallbacks(sc, callbackInfo.callbackIds);
+    TransactionCompletedListener::getInstance()->addSurfaceControlToCallbacks(callbackInfo, sc);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
@@ -1592,7 +1604,7 @@
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer(
         const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
         const std::optional<sp<Fence>>& fence, const std::optional<uint64_t>& optFrameNumber,
-        ReleaseBufferCallback callback) {
+        uint32_t producerId, ReleaseBufferCallback callback) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1611,6 +1623,7 @@
     bufferData->buffer = buffer;
     uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber);
     bufferData->frameNumber = frameNumber;
+    bufferData->producerId = producerId;
     bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged;
     if (fence) {
         bufferData->acquireFence = *fence;
@@ -1673,6 +1686,21 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExtendedRangeBrightness(
+        const sp<SurfaceControl>& sc, float currentBufferRatio, float desiredRatio) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eExtendedRangeBrightnessChanged;
+    s->currentSdrHdrRatio = currentBufferRatio;
+    s->desiredSdrHdrRatio = desiredRatio;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setHdrMetadata(
         const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata) {
     layer_state_t* s = getLayerState(sc);
@@ -2977,6 +3005,7 @@
     while (true) {
         {
             std::unique_lock<std::mutex> lock(mMutex);
+            base::ScopedLockAssertion assumeLocked(mMutex);
             callbackInfos = std::move(mCallbackInfos);
             mCallbackInfos = {};
         }
@@ -2989,6 +3018,7 @@
 
         {
             std::unique_lock<std::mutex> lock(mMutex);
+            base::ScopedLockAssertion assumeLocked(mMutex);
             if (mCallbackInfos.size() == 0) {
                 mReleaseCallbackPending.wait(lock);
             }
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index 7aee882..c5f9c38 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -26,6 +26,7 @@
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
 #include <utils/Log.h>
+#include <utils/Looper.h>
 #include <utils/threads.h>
 
 #include <binder/IPCThreadState.h>
@@ -34,8 +35,9 @@
 #include <ui/Rect.h>
 #include <ui/StaticDisplayInfo.h>
 
-#include <gui/BufferQueueCore.h>
 #include <gui/BLASTBufferQueue.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/Choreographer.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
@@ -191,6 +193,24 @@
     return mName;
 }
 
+std::shared_ptr<Choreographer> SurfaceControl::getChoreographer() {
+    if (mChoreographer) {
+        return mChoreographer;
+    }
+    sp<Looper> looper = Looper::getForThread();
+    if (!looper.get()) {
+        ALOGE("%s: No looper prepared for thread", __func__);
+        return nullptr;
+    }
+    mChoreographer = std::make_shared<Choreographer>(looper, getHandle());
+    status_t result = mChoreographer->initialize();
+    if (result != OK) {
+        ALOGE("Failed to initialize choreographer");
+        mChoreographer = nullptr;
+    }
+    return mChoreographer;
+}
+
 sp<IGraphicBufferProducer> SurfaceControl::getIGraphicBufferProducer()
 {
     getSurface();
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index c08a7c6..9812142 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -68,9 +68,15 @@
 
     /**
      * Create a display event connection
+     *
+     * layerHandle
+     *     Optional binder handle representing a Layer in SF to associate the new
+     *     DisplayEventConnection with. This handle can be found inside a surface control after
+     *     surface creation, see ISurfaceComposerClient::createSurface. Set to null if no layer
+     *     association should be made.
      */
     @nullable IDisplayEventConnection createDisplayEventConnection(VsyncSource vsyncSource,
-            EventRegistration eventRegistration);
+            EventRegistration eventRegistration, @nullable IBinder layerHandle);
 
     /**
      * Create a connection with SurfaceFlinger.
@@ -79,9 +85,21 @@
 
     /**
      * Create a virtual display
+     *
+     * displayName
+     *     The name of the virtual display
+     * secure
+     *     Whether this virtual display is secure
+     * 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
+     *     with physical displays running concurrently. If 0 is specified, the
+     *     virtual display is refreshed at the physical display refresh rate.
+     *
      * requires ACCESS_SURFACE_FLINGER permission.
      */
-    @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure);
+    @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure,
+            float requestedRefreshRate);
 
     /**
      * Destroy a virtual display
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 685bd92..f01c2a9 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -64,10 +64,10 @@
     MOCK_METHOD(binder::Status, bootFinished, (), (override));
     MOCK_METHOD(binder::Status, createDisplayEventConnection,
                 (gui::ISurfaceComposer::VsyncSource, gui::ISurfaceComposer::EventRegistration,
-                 sp<gui::IDisplayEventConnection>*),
+                 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, sp<IBinder>*),
+    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));
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index c93ab86..b9e0647 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -44,23 +44,23 @@
             mCurrentlyConnected(false),
             mPreviouslyConnected(false) {}
 
-    void onDisconnect() override;
+    void onDisconnect() override EXCLUDES(mMutex);
     void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
-                                  FrameEventHistoryDelta* outDelta) override REQUIRES(mMutex);
+                                  FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex);
     void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
                                const sp<Fence>& gpuCompositionDoneFence,
                                const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
                                CompositorTiming compositorTiming, nsecs_t latchTime,
-                               nsecs_t dequeueReadyTime) REQUIRES(mMutex);
-    void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect);
+                               nsecs_t dequeueReadyTime) EXCLUDES(mMutex);
+    void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect) EXCLUDES(mMutex);
 
 protected:
-    void onSidebandStreamChanged() override REQUIRES(mMutex);
+    void onSidebandStreamChanged() override EXCLUDES(mMutex);
 
 private:
     const wp<BLASTBufferQueue> mBLASTBufferQueue;
 
-    uint64_t mCurrentFrameNumber = 0;
+    uint64_t mCurrentFrameNumber GUARDED_BY(mMutex) = 0;
 
     Mutex mMutex;
     ConsumerFrameEventHistory mFrameEventHistory GUARDED_BY(mMutex);
@@ -94,7 +94,7 @@
                                std::optional<uint32_t> currentMaxAcquiredBufferCount);
     void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                      std::optional<uint32_t> currentMaxAcquiredBufferCount,
-                                     bool fakeRelease);
+                                     bool fakeRelease) REQUIRES(mMutex);
     void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                              bool acquireSingleBuffer = true);
     void stopContinuousSyncTransaction();
@@ -150,7 +150,7 @@
     // mNumAcquired (buffers that queued to SF)  mPendingRelease.size() (buffers that are held by
     // blast). This counter is read by android studio profiler.
     std::string mQueuedBufferTrace;
-    sp<SurfaceControl> mSurfaceControl;
+    sp<SurfaceControl> mSurfaceControl GUARDED_BY(mMutex);
 
     mutable std::mutex mMutex;
     std::condition_variable mCallbackCV;
@@ -162,6 +162,11 @@
     int32_t mNumFrameAvailable GUARDED_BY(mMutex) = 0;
     int32_t mNumAcquired GUARDED_BY(mMutex) = 0;
 
+    // A value used to identify if a producer has been changed for the same SurfaceControl.
+    // This is needed to know when the frame number has been reset to make sure we don't
+    // latch stale buffers and that we don't wait on barriers from an old producer.
+    uint32_t mProducerId = 0;
+
     // Keep a reference to the submitted buffers so we can release when surfaceflinger drops the
     // buffer or the buffer has been presented and a new buffer is ready to be presented.
     std::unordered_map<ReleaseCallbackId, BufferItem, ReleaseBufferCallbackIdHash> mSubmitted
@@ -252,7 +257,7 @@
     // callback for them.
     std::queue<sp<SurfaceControl>> mSurfaceControlsWithPendingCallback GUARDED_BY(mMutex);
 
-    uint32_t mCurrentMaxAcquiredBufferCount;
+    uint32_t mCurrentMaxAcquiredBufferCount GUARDED_BY(mMutex);
 
     // Flag to determine if syncTransaction should only acquire a single buffer and then clear or
     // continue to acquire buffers until explicitly cleared
@@ -276,8 +281,8 @@
     // need to set this flag, notably only in the case where we are transitioning from a previous
     // transaction applied by us (one way, may not yet have reached server) and an upcoming
     // transaction that will be applied by some sync consumer.
-    bool mAppliedLastTransaction = false;
-    uint64_t mLastAppliedFrameNumber = 0;
+    bool mAppliedLastTransaction GUARDED_BY(mMutex) = false;
+    uint64_t mLastAppliedFrameNumber GUARDED_BY(mMutex) = 0;
 
     std::function<void(const std::string&)> mTransactionHangCallback;
 
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 89a7058..1df9b11 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -73,7 +73,8 @@
     };
     static Context gChoreographers;
 
-    explicit Choreographer(const sp<Looper>& looper) EXCLUDES(gChoreographers.lock);
+    explicit Choreographer(const sp<Looper>& looper, const sp<IBinder>& layerHandle = nullptr)
+            EXCLUDES(gChoreographers.lock);
     void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                   AChoreographer_frameCallback64 cb64,
                                   AChoreographer_vsyncCallback vsyncCallback, void* data,
diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
index ec884cf..c826c17 100644
--- a/libs/gui/include/gui/DisplayCaptureArgs.h
+++ b/libs/gui/include/gui/DisplayCaptureArgs.h
@@ -24,6 +24,7 @@
 #include <binder/Parcelable.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
+#include <ui/Rect.h>
 
 namespace android::gui {
 
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index bf3a07b..140efa6 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -26,7 +26,8 @@
     explicit DisplayEventDispatcher(const sp<Looper>& looper,
                                     gui::ISurfaceComposer::VsyncSource vsyncSource =
                                             gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
-                                    EventRegistrationFlags eventRegistration = {});
+                                    EventRegistrationFlags eventRegistration = {},
+                                    const sp<IBinder>& layerHandle = nullptr);
 
     status_t initialize();
     void dispose();
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 0f4907f..7fd6c35 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -119,7 +119,8 @@
      */
     explicit DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource =
                                           gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
-                                  EventRegistrationFlags eventRegistration = {});
+                                  EventRegistrationFlags eventRegistration = {},
+                                  const sp<IBinder>& layerHandle = nullptr);
 
     /*
      * ~DisplayEventReceiver severs the connection with SurfaceFlinger, new events
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index ae56f9f..045cc2a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -113,9 +113,8 @@
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
             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>& uncacheBuffer,
-            bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
-            uint64_t transactionId) = 0;
+            bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+            const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index d593f56..39bcb4a 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -40,10 +40,15 @@
 class CallbackId : public Parcelable {
 public:
     int64_t id;
-    enum class Type : int32_t { ON_COMPLETE, ON_COMMIT } type;
+    enum class Type : int32_t {
+        ON_COMPLETE = 0,
+        ON_COMMIT = 1,
+        /*reserved for serialization = 2*/
+    } type;
+    bool includeJankData; // Only respected for ON_COMPLETE callbacks.
 
     CallbackId() {}
-    CallbackId(int64_t id, Type type) : id(id), type(type) {}
+    CallbackId(int64_t id, Type type) : id(id), type(type), includeJankData(false) {}
     status_t writeToParcel(Parcel* output) const override;
     status_t readFromParcel(const Parcel* input) override;
 
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 70c9daf..ddaf473 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -111,6 +111,7 @@
     uint64_t frameNumber = 0;
     bool hasBarrier = false;
     uint64_t barrierFrameNumber = 0;
+    uint32_t producerId = 0;
 
     // Listens to when the buffer is safe to be released. This is used for blast
     // layers only. The callback includes a release fence as well as the graphic
@@ -170,7 +171,7 @@
         eTransparentRegionChanged = 0x00000020,
         eFlagsChanged = 0x00000040,
         eLayerStackChanged = 0x00000080,
-        /* unused = 0x00000100, */
+        eFlushJankData = 0x00000100,
         /* unused = 0x00000200, */
         eDimmingEnabledChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
@@ -209,7 +210,8 @@
         eAutoRefreshChanged = 0x1000'00000000,
         eStretchChanged = 0x2000'00000000,
         eTrustedOverlayChanged = 0x4000'00000000,
-        eDropInputModeChanged = 0x8000'00000000
+        eDropInputModeChanged = 0x8000'00000000,
+        eExtendedRangeBrightnessChanged = 0x10000'00000000
     };
 
     layer_state_t();
@@ -240,7 +242,8 @@
             layer_state_t::eBufferTransformChanged | layer_state_t::eDataspaceChanged |
             layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged |
             layer_state_t::eTransformToDisplayInverseChanged |
-            layer_state_t::eTransparentRegionChanged;
+            layer_state_t::eTransparentRegionChanged |
+            layer_state_t::eExtendedRangeBrightnessChanged;
 
     // Content updates.
     static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES |
@@ -385,6 +388,8 @@
     gui::DropInputMode dropInputMode;
 
     bool dimmingEnabled;
+    float currentSdrHdrRatio = 1.f;
+    float desiredSdrHdrRatio = 1.f;
 
     TrustedPresentationThresholds trustedPresentationThresholds;
     TrustedPresentationListener trustedPresentationListener;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 27353fb..63c0505 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -357,7 +357,8 @@
     sp<SurfaceControl> mirrorDisplay(DisplayId displayId);
 
     //! Create a virtual display
-    static sp<IBinder> createDisplay(const String8& displayName, bool secure);
+    static sp<IBinder> createDisplay(const String8& displayName, bool secure,
+                                     float requestedRefereshRate = 0);
 
     //! Destroy a virtual display
     static void destroyDisplay(const sp<IBinder>& display);
@@ -414,7 +415,6 @@
         SortedVector<DisplayState> mDisplayStates;
         std::unordered_map<sp<ITransactionCompletedListener>, CallbackInfo, TCLHash>
                 mListenerCallbacks;
-        std::vector<client_cache_t> mUncacheBuffers;
 
         uint64_t mId;
 
@@ -537,7 +537,7 @@
         Transaction& setBuffer(const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
                                const std::optional<sp<Fence>>& fence = std::nullopt,
                                const std::optional<uint64_t>& frameNumber = std::nullopt,
-                               ReleaseBufferCallback callback = nullptr);
+                               uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr);
         std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
 
         /**
@@ -561,6 +561,8 @@
         Transaction& setBufferHasBarrier(const sp<SurfaceControl>& sc,
                                          uint64_t barrierFrameNumber);
         Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace);
+        Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc,
+                                                float currentBufferRatio, float desiredRatio);
         Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
         Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
                                             const Region& surfaceDamageRegion);
@@ -741,6 +743,8 @@
 
         static sp<IBinder> getDefaultApplyToken();
         static void setDefaultApplyToken(sp<IBinder> applyToken);
+
+        static status_t sendSurfaceFlushJankDataTransaction(const sp<SurfaceControl>& sc);
     };
 
     status_t clearLayerFrameStats(const sp<IBinder>& token) const;
@@ -875,10 +879,14 @@
             const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
                     surfaceControls,
             CallbackId::Type callbackType);
+    CallbackId addCallbackFunctionLocked(
+            const TransactionCompletedCallback& callbackFunction,
+            const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
+                    surfaceControls,
+            CallbackId::Type callbackType) REQUIRES(mMutex);
 
-    void addSurfaceControlToCallbacks(
-            const sp<SurfaceControl>& surfaceControl,
-            const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds);
+    void addSurfaceControlToCallbacks(SurfaceComposerClient::CallbackInfo& callbackInfo,
+                                      const sp<SurfaceControl>& surfaceControl);
 
     void addQueueStallListener(std::function<void(const std::string&)> stallListener, void* id);
     void removeQueueStallListener(void *id);
@@ -920,7 +928,7 @@
     void onTrustedPresentationChanged(int id, bool presentedWithinThresholds) override;
 
 private:
-    ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&);
+    ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&) REQUIRES(mMutex);
     static sp<TransactionCompletedListener> sInstance;
 };
 
diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h
index 1d4fc7f..344b957 100644
--- a/libs/gui/include/gui/SurfaceControl.h
+++ b/libs/gui/include/gui/SurfaceControl.h
@@ -36,6 +36,7 @@
 
 // ---------------------------------------------------------------------------
 
+class Choreographer;
 class IGraphicBufferProducer;
 class Surface;
 class SurfaceComposerClient;
@@ -80,6 +81,9 @@
     int32_t getLayerId() const;
     const std::string& getName() const;
 
+    // TODO(b/267195698): Consider renaming.
+    std::shared_ptr<Choreographer> getChoreographer();
+
     sp<IGraphicBufferProducer> getIGraphicBufferProducer();
 
     status_t clearLayerFrameStats() const;
@@ -130,6 +134,7 @@
     PixelFormat mFormat = PIXEL_FORMAT_NONE;
     uint32_t mCreateFlags = 0;
     uint64_t mFallbackFrameNumber = 100;
+    std::shared_ptr<Choreographer> mChoreographer;
 };
 
 }; // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 32d60cd..be1ef8e 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -701,7 +701,7 @@
                                  const sp<IBinder>& /*applyToken*/,
                                  const InputWindowCommands& /*inputWindowCommands*/,
                                  int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                                 const std::vector<client_cache_t>& /*cachedBuffer*/,
+                                 const client_cache_t& /*cachedBuffer*/,
                                  bool /*hasListenerCallbacks*/,
                                  const std::vector<ListenerCallbacks>& /*listenerCallbacks*/,
                                  uint64_t /*transactionId*/) override {
@@ -725,6 +725,7 @@
 
     binder::Status createDisplayEventConnection(
             VsyncSource /*vsyncSource*/, EventRegistration /*eventRegistration*/,
+            const sp<IBinder>& /*layerHandle*/,
             sp<gui::IDisplayEventConnection>* outConnection) override {
         *outConnection = nullptr;
         return binder::Status::ok();
@@ -736,6 +737,7 @@
     }
 
     binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/,
+                                 float /*requestedRefreshRate*/,
                                  sp<IBinder>* /*outDisplay*/) override {
         return binder::Status::ok();
     }
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 8f41cc1..fd4fc16 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -41,6 +41,7 @@
         "-Wall",
         "-Wextra",
         "-Werror",
+        "-Wno-unused-parameter",
     ],
     srcs: [
         "Input.cpp",
@@ -52,13 +53,18 @@
         "MotionPredictor.cpp",
         "PrintTools.cpp",
         "PropertyMap.cpp",
+        "TfLiteMotionPredictor.cpp",
         "TouchVideoFrame.cpp",
         "VelocityControl.cpp",
         "VelocityTracker.cpp",
         "VirtualKeyMap.cpp",
     ],
 
-    header_libs: ["jni_headers"],
+    header_libs: [
+        "flatbuffer_headers",
+        "jni_headers",
+        "tensorflow_headers",
+    ],
     export_header_lib_headers: ["jni_headers"],
 
     shared_libs: [
@@ -69,8 +75,13 @@
         "libvintf",
     ],
 
+    ldflags: [
+        "-Wl,--exclude-libs=libtflite_static.a",
+    ],
+
     static_libs: [
         "libui-types",
+        "libtflite_static",
     ],
 
     export_static_lib_headers: [
@@ -103,6 +114,10 @@
             sanitize: {
                 misc_undefined: ["integer"],
             },
+
+            required: [
+                "motion_predictor_model_prebuilt",
+            ],
         },
         host: {
             shared: {
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 0fa0f12..7d11ef2 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,118 +18,193 @@
 
 #include <input/MotionPredictor.h>
 
+#include <cinttypes>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <android/input.h>
+#include <log/log.h>
+
+#include <attestation/HmacKeyManager.h>
+#include <input/TfLiteMotionPredictor.h>
+
+namespace android {
+namespace {
+
+const char DEFAULT_MODEL_PATH[] = "/system/etc/motion_predictor_model.fb";
+const int64_t PREDICTION_INTERVAL_NANOS =
+        12500000 / 3; // TODO(b/266747937): Get this from the model.
+
 /**
  * Log debug messages about predictions.
  * Enable this via "adb shell setprop log.tag.MotionPredictor DEBUG"
  */
-static bool isDebug() {
+bool isDebug() {
     return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
 }
 
-namespace android {
+// Converts a prediction of some polar (r, phi) to Cartesian (x, y) when applied to an axis.
+TfLiteMotionPredictorSample::Point convertPrediction(
+        const TfLiteMotionPredictorSample::Point& axisFrom,
+        const TfLiteMotionPredictorSample::Point& axisTo, float r, float phi) {
+    const TfLiteMotionPredictorSample::Point axis = axisTo - axisFrom;
+    const float axis_phi = std::atan2(axis.y, axis.x);
+    const float x_delta = r * std::cos(axis_phi + phi);
+    const float y_delta = r * std::sin(axis_phi + phi);
+    return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta};
+}
+
+} // namespace
 
 // --- MotionPredictor ---
 
-MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath,
                                  std::function<bool()> checkMotionPredictionEnabled)
       : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
+        mModelPath(modelPath == nullptr ? DEFAULT_MODEL_PATH : modelPath),
         mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
 
 void MotionPredictor::record(const MotionEvent& event) {
-    mEvents.push_back({});
-    mEvents.back().copyFrom(&event, /*keepHistory=*/true);
-    if (mEvents.size() > 2) {
-        // Just need 2 samples in order to extrapolate
-        mEvents.erase(mEvents.begin());
+    if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
+        ALOGE("Prediction not supported for device %d's %s source", event.getDeviceId(),
+              inputEventSourceToString(event.getSource()).c_str());
+        return;
     }
+
+    // Initialise the model now that it's likely to be used.
+    if (!mModel) {
+        mModel = TfLiteMotionPredictorModel::create(mModelPath.c_str());
+    }
+
+    TfLiteMotionPredictorBuffers& buffers =
+            mDeviceBuffers.try_emplace(event.getDeviceId(), mModel->inputLength()).first->second;
+
+    const int32_t action = event.getActionMasked();
+    if (action == AMOTION_EVENT_ACTION_UP) {
+        ALOGD_IF(isDebug(), "End of event stream");
+        buffers.reset();
+        return;
+    } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) {
+        ALOGD_IF(isDebug(), "Skipping unsupported %s action",
+                 MotionEvent::actionToString(action).c_str());
+        return;
+    }
+
+    if (event.getPointerCount() != 1) {
+        ALOGD_IF(isDebug(), "Prediction not supported for multiple pointers");
+        return;
+    }
+
+    const int32_t toolType = event.getPointerProperties(0)->toolType;
+    if (toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS) {
+        ALOGD_IF(isDebug(), "Prediction not supported for non-stylus tool: %s",
+                 motionToolTypeToString(toolType));
+        return;
+    }
+
+    for (size_t i = 0; i <= event.getHistorySize(); ++i) {
+        if (event.isResampled(0, i)) {
+            continue;
+        }
+        const PointerCoords* coords = event.getHistoricalRawPointerCoords(0, i);
+        buffers.pushSample(event.getHistoricalEventTime(i),
+                           {
+                                   .position.x = coords->getAxisValue(AMOTION_EVENT_AXIS_X),
+                                   .position.y = coords->getAxisValue(AMOTION_EVENT_AXIS_Y),
+                                   .pressure = event.getHistoricalPressure(0, i),
+                                   .tilt = event.getHistoricalAxisValue(AMOTION_EVENT_AXIS_TILT, 0,
+                                                                        i),
+                                   .orientation = event.getHistoricalOrientation(0, i),
+                           });
+    }
+
+    mLastEvents.try_emplace(event.getDeviceId())
+            .first->second.copyFrom(&event, /*keepHistory=*/false);
 }
 
-/**
- * This is an example implementation that should be replaced with the actual prediction.
- * The returned MotionEvent should be similar to the incoming MotionEvent, except for the
- * fields that are predicted:
- *
- * 1) event.getEventTime
- * 2) event.getPointerCoords
- *
- * The returned event should not contain any of the real, existing data. It should only
- * contain the predicted samples.
- */
 std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) {
-    if (mEvents.size() < 2) {
-        return {};
-    }
+    std::vector<std::unique_ptr<MotionEvent>> predictions;
 
-    const MotionEvent& event = mEvents.back();
-    if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
-        return {};
-    }
-
-    std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
-    std::vector<PointerCoords> futureCoords;
-    const nsecs_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
-    const nsecs_t currentTime = event.getEventTime();
-    const MotionEvent& previous = mEvents.rbegin()[1];
-    const nsecs_t oldTime = previous.getEventTime();
-    if (currentTime == oldTime) {
-        // This can happen if it's an ACTION_POINTER_DOWN event, for example.
-        return {}; // prevent division by zero.
-    }
-
-    for (size_t i = 0; i < event.getPointerCount(); i++) {
-        const int32_t pointerId = event.getPointerId(i);
-        const PointerCoords* currentPointerCoords = event.getRawPointerCoords(i);
-        const float currentX = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
-        const float currentY = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
-
-        PointerCoords coords;
-        coords.clear();
-
-        ssize_t index = previous.findPointerIndex(pointerId);
-        if (index >= 0) {
-            // We have old data for this pointer. Compute the prediction.
-            const PointerCoords* oldPointerCoords = previous.getRawPointerCoords(index);
-            const float oldX = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
-            const float oldY = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
-
-            // Let's do a linear interpolation while waiting for a real model
-            const float scale =
-                    static_cast<float>(futureTime - currentTime) / (currentTime - oldTime);
-            const float futureX = currentX + (currentX - oldX) * scale;
-            const float futureY = currentY + (currentY - oldY) * scale;
-
-            coords.setAxisValue(AMOTION_EVENT_AXIS_X, futureX);
-            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, futureY);
-            ALOGD_IF(isDebug(),
-                     "Prediction by %.1f ms, (%.1f, %.1f), (%.1f, %.1f) --> (%.1f, %.1f)",
-                     (futureTime - event.getEventTime()) * 1E-6, oldX, oldY, currentX, currentY,
-                     futureX, futureY);
+    for (const auto& [deviceId, buffer] : mDeviceBuffers) {
+        if (!buffer.isReady()) {
+            continue;
         }
 
-        futureCoords.push_back(coords);
+        LOG_ALWAYS_FATAL_IF(!mModel);
+        buffer.copyTo(*mModel);
+        LOG_ALWAYS_FATAL_IF(!mModel->invoke());
+
+        // Read out the predictions.
+        const std::span<const float> predictedR = mModel->outputR();
+        const std::span<const float> predictedPhi = mModel->outputPhi();
+        const std::span<const float> predictedPressure = mModel->outputPressure();
+
+        TfLiteMotionPredictorSample::Point axisFrom = buffer.axisFrom().position;
+        TfLiteMotionPredictorSample::Point axisTo = buffer.axisTo().position;
+
+        if (isDebug()) {
+            ALOGD("deviceId: %d", deviceId);
+            ALOGD("axisFrom: %f, %f", axisFrom.x, axisFrom.y);
+            ALOGD("axisTo: %f, %f", axisTo.x, axisTo.y);
+            ALOGD("mInputR: %s", base::Join(mModel->inputR(), ", ").c_str());
+            ALOGD("mInputPhi: %s", base::Join(mModel->inputPhi(), ", ").c_str());
+            ALOGD("mInputPressure: %s", base::Join(mModel->inputPressure(), ", ").c_str());
+            ALOGD("mInputTilt: %s", base::Join(mModel->inputTilt(), ", ").c_str());
+            ALOGD("mInputOrientation: %s", base::Join(mModel->inputOrientation(), ", ").c_str());
+            ALOGD("predictedR: %s", base::Join(predictedR, ", ").c_str());
+            ALOGD("predictedPhi: %s", base::Join(predictedPhi, ", ").c_str());
+            ALOGD("predictedPressure: %s", base::Join(predictedPressure, ", ").c_str());
+        }
+
+        const MotionEvent& event = mLastEvents[deviceId];
+        bool hasPredictions = false;
+        std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
+        int64_t predictionTime = buffer.lastTimestamp();
+        const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
+
+        for (int i = 0; i < predictedR.size() && predictionTime <= futureTime; ++i) {
+            const TfLiteMotionPredictorSample::Point point =
+                    convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
+            // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+
+            ALOGD_IF(isDebug(), "prediction %d: %f, %f", i, point.x, point.y);
+            PointerCoords coords;
+            coords.clear();
+            coords.setAxisValue(AMOTION_EVENT_AXIS_X, point.x);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, point.y);
+            // TODO(b/266747654): Stop predictions if predicted pressure is < some threshold.
+            coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
+
+            predictionTime += PREDICTION_INTERVAL_NANOS;
+            if (i == 0) {
+                hasPredictions = true;
+                prediction->initialize(InputEvent::nextId(), event.getDeviceId(), event.getSource(),
+                                       event.getDisplayId(), INVALID_HMAC,
+                                       AMOTION_EVENT_ACTION_MOVE, event.getActionButton(),
+                                       event.getFlags(), event.getEdgeFlags(), event.getMetaState(),
+                                       event.getButtonState(), event.getClassification(),
+                                       event.getTransform(), event.getXPrecision(),
+                                       event.getYPrecision(), event.getRawXCursorPosition(),
+                                       event.getRawYCursorPosition(), event.getRawTransform(),
+                                       event.getDownTime(), predictionTime, event.getPointerCount(),
+                                       event.getPointerProperties(), &coords);
+            } else {
+                prediction->addSample(predictionTime, &coords);
+            }
+
+            axisFrom = axisTo;
+            axisTo = point;
+        }
+        // TODO(b/266747511): Interpolate to futureTime?
+        if (hasPredictions) {
+            predictions.push_back(std::move(prediction));
+        }
     }
-
-    /**
-     * The process of adding samples is different for the first and subsequent samples:
-     * 1. Add the first sample via 'initialize' as below
-     * 2. Add subsequent samples via 'addSample'
-     */
-    prediction->initialize(event.getId(), event.getDeviceId(), event.getSource(),
-                           event.getDisplayId(), event.getHmac(), event.getAction(),
-                           event.getActionButton(), event.getFlags(), event.getEdgeFlags(),
-                           event.getMetaState(), event.getButtonState(), event.getClassification(),
-                           event.getTransform(), event.getXPrecision(), event.getYPrecision(),
-                           event.getRawXCursorPosition(), event.getRawYCursorPosition(),
-                           event.getRawTransform(), event.getDownTime(), futureTime,
-                           event.getPointerCount(), event.getPointerProperties(),
-                           futureCoords.data());
-
-    // To add more predicted samples, use 'addSample':
-    prediction->addSample(futureTime + 1, futureCoords.data());
-
-    std::vector<std::unique_ptr<MotionEvent>> out;
-    out.push_back(std::move(prediction));
-    return out;
+    return predictions;
 }
 
 bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) {
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
new file mode 100644
index 0000000..10510d6
--- /dev/null
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -0,0 +1,362 @@
+/*
+ * 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 "TfLiteMotionPredictor"
+#include <input/TfLiteMotionPredictor.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <span>
+#include <type_traits>
+#include <utility>
+
+#include <android-base/logging.h>
+#include <android-base/mapped_file.h>
+#define ATRACE_TAG ATRACE_TAG_INPUT
+#include <cutils/trace.h>
+#include <log/log.h>
+
+#include "tensorflow/lite/core/api/error_reporter.h"
+#include "tensorflow/lite/core/api/op_resolver.h"
+#include "tensorflow/lite/interpreter.h"
+#include "tensorflow/lite/kernels/builtin_op_kernels.h"
+#include "tensorflow/lite/model.h"
+#include "tensorflow/lite/mutable_op_resolver.h"
+
+namespace android {
+namespace {
+
+constexpr char SIGNATURE_KEY[] = "serving_default";
+
+// Input tensor names.
+constexpr char INPUT_R[] = "r";
+constexpr char INPUT_PHI[] = "phi";
+constexpr char INPUT_PRESSURE[] = "pressure";
+constexpr char INPUT_TILT[] = "tilt";
+constexpr char INPUT_ORIENTATION[] = "orientation";
+
+// Output tensor names.
+constexpr char OUTPUT_R[] = "r";
+constexpr char OUTPUT_PHI[] = "phi";
+constexpr char OUTPUT_PRESSURE[] = "pressure";
+
+// A TFLite ErrorReporter that logs to logcat.
+class LoggingErrorReporter : public tflite::ErrorReporter {
+public:
+    int Report(const char* format, va_list args) override {
+        return LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, format, args);
+    }
+};
+
+// Searches a runner for an input tensor.
+TfLiteTensor* findInputTensor(const char* name, tflite::SignatureRunner* runner) {
+    TfLiteTensor* tensor = runner->input_tensor(name);
+    LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find input tensor '%s'", name);
+    return tensor;
+}
+
+// Searches a runner for an output tensor.
+const TfLiteTensor* findOutputTensor(const char* name, tflite::SignatureRunner* runner) {
+    const TfLiteTensor* tensor = runner->output_tensor(name);
+    LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find output tensor '%s'", name);
+    return tensor;
+}
+
+// Returns the buffer for a tensor of type T.
+template <typename T>
+std::span<T> getTensorBuffer(typename std::conditional<std::is_const<T>::value, const TfLiteTensor*,
+                                                       TfLiteTensor*>::type tensor) {
+    LOG_ALWAYS_FATAL_IF(!tensor);
+
+    const TfLiteType type = tflite::typeToTfLiteType<typename std::remove_cv<T>::type>();
+    LOG_ALWAYS_FATAL_IF(tensor->type != type, "Unexpected type for '%s' tensor: %s (expected %s)",
+                        tensor->name, TfLiteTypeGetName(tensor->type), TfLiteTypeGetName(type));
+
+    LOG_ALWAYS_FATAL_IF(!tensor->data.data);
+    return {reinterpret_cast<T*>(tensor->data.data),
+            static_cast<typename std::span<T>::index_type>(tensor->bytes / sizeof(T))};
+}
+
+// Verifies that a tensor exists and has an underlying buffer of type T.
+template <typename T>
+void checkTensor(const TfLiteTensor* tensor) {
+    LOG_ALWAYS_FATAL_IF(!tensor);
+
+    const auto buffer = getTensorBuffer<const T>(tensor);
+    LOG_ALWAYS_FATAL_IF(buffer.empty(), "No buffer for tensor '%s'", tensor->name);
+}
+
+std::unique_ptr<tflite::OpResolver> createOpResolver() {
+    auto resolver = std::make_unique<tflite::MutableOpResolver>();
+    resolver->AddBuiltin(::tflite::BuiltinOperator_CONCATENATION,
+                         ::tflite::ops::builtin::Register_CONCATENATION());
+    resolver->AddBuiltin(::tflite::BuiltinOperator_FULLY_CONNECTED,
+                         ::tflite::ops::builtin::Register_FULLY_CONNECTED());
+    return resolver;
+}
+
+} // namespace
+
+TfLiteMotionPredictorBuffers::TfLiteMotionPredictorBuffers(size_t inputLength)
+      : mInputR(inputLength, 0),
+        mInputPhi(inputLength, 0),
+        mInputPressure(inputLength, 0),
+        mInputTilt(inputLength, 0),
+        mInputOrientation(inputLength, 0) {
+    LOG_ALWAYS_FATAL_IF(inputLength == 0, "Buffer input size must be greater than 0");
+}
+
+void TfLiteMotionPredictorBuffers::reset() {
+    std::fill(mInputR.begin(), mInputR.end(), 0);
+    std::fill(mInputPhi.begin(), mInputPhi.end(), 0);
+    std::fill(mInputPressure.begin(), mInputPressure.end(), 0);
+    std::fill(mInputTilt.begin(), mInputTilt.end(), 0);
+    std::fill(mInputOrientation.begin(), mInputOrientation.end(), 0);
+    mAxisFrom.reset();
+    mAxisTo.reset();
+}
+
+void TfLiteMotionPredictorBuffers::copyTo(TfLiteMotionPredictorModel& model) const {
+    LOG_ALWAYS_FATAL_IF(mInputR.size() != model.inputLength(),
+                        "Buffer length %zu doesn't match model input length %zu", mInputR.size(),
+                        model.inputLength());
+    LOG_ALWAYS_FATAL_IF(!isReady(), "Buffers are incomplete");
+
+    std::copy(mInputR.begin(), mInputR.end(), model.inputR().begin());
+    std::copy(mInputPhi.begin(), mInputPhi.end(), model.inputPhi().begin());
+    std::copy(mInputPressure.begin(), mInputPressure.end(), model.inputPressure().begin());
+    std::copy(mInputTilt.begin(), mInputTilt.end(), model.inputTilt().begin());
+    std::copy(mInputOrientation.begin(), mInputOrientation.end(), model.inputOrientation().begin());
+}
+
+void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp,
+                                              const TfLiteMotionPredictorSample sample) {
+    // Convert the sample (x, y) into polar (r, φ) based on a reference axis
+    // from the preceding two points (mAxisFrom/mAxisTo).
+
+    mTimestamp = timestamp;
+
+    if (!mAxisTo) { // First point.
+        mAxisTo = sample;
+        return;
+    }
+
+    // Vector from the last point to the current sample point.
+    const TfLiteMotionPredictorSample::Point v = sample.position - mAxisTo->position;
+
+    const float r = std::hypot(v.x, v.y);
+    float phi = 0;
+    float orientation = 0;
+
+    // Ignore the sample if there is no movement. These samples can occur when there's change to a
+    // property other than the coordinates and pollute the input to the model.
+    if (r == 0) {
+        return;
+    }
+
+    if (!mAxisFrom) { // Second point.
+        // We can only determine the distance from the first point, and not any
+        // angle. However, if the second point forms an axis, the orientation can
+        // be transformed relative to that axis.
+        const float axisPhi = std::atan2(v.y, v.x);
+        // A MotionEvent's orientation is measured clockwise from the vertical
+        // axis, but axisPhi is measured counter-clockwise from the horizontal
+        // axis.
+        orientation = M_PI_2 - sample.orientation - axisPhi;
+    } else {
+        const TfLiteMotionPredictorSample::Point axis = mAxisTo->position - mAxisFrom->position;
+        const float axisPhi = std::atan2(axis.y, axis.x);
+        phi = std::atan2(v.y, v.x) - axisPhi;
+
+        if (std::hypot(axis.x, axis.y) > 0) {
+            // See note above.
+            orientation = M_PI_2 - sample.orientation - axisPhi;
+        }
+    }
+
+    // Update the axis for the next point.
+    mAxisFrom = mAxisTo;
+    mAxisTo = sample;
+
+    // Push the current sample onto the end of the input buffers.
+    mInputR.pushBack(r);
+    mInputPhi.pushBack(phi);
+    mInputPressure.pushBack(sample.pressure);
+    mInputTilt.pushBack(sample.tilt);
+    mInputOrientation.pushBack(orientation);
+}
+
+std::unique_ptr<TfLiteMotionPredictorModel> TfLiteMotionPredictorModel::create(
+        const char* modelPath) {
+    const int fd = open(modelPath, O_RDONLY);
+    if (fd == -1) {
+        PLOG(FATAL) << "Could not read model from " << modelPath;
+    }
+
+    const off_t fdSize = lseek(fd, 0, SEEK_END);
+    if (fdSize == -1) {
+        PLOG(FATAL) << "Failed to determine file size";
+    }
+
+    std::unique_ptr<android::base::MappedFile> modelBuffer =
+            android::base::MappedFile::FromFd(fd, /*offset=*/0, fdSize, PROT_READ);
+    if (!modelBuffer) {
+        PLOG(FATAL) << "Failed to mmap model";
+    }
+    if (close(fd) == -1) {
+        PLOG(FATAL) << "Failed to close model fd";
+    }
+
+    return std::unique_ptr<TfLiteMotionPredictorModel>(
+            new TfLiteMotionPredictorModel(std::move(modelBuffer)));
+}
+
+TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(
+        std::unique_ptr<android::base::MappedFile> model)
+      : mFlatBuffer(std::move(model)) {
+    CHECK(mFlatBuffer);
+    mErrorReporter = std::make_unique<LoggingErrorReporter>();
+    mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer->data(),
+                                                               mFlatBuffer->size(),
+                                                               /*extra_verifier=*/nullptr,
+                                                               mErrorReporter.get());
+    LOG_ALWAYS_FATAL_IF(!mModel);
+
+    auto resolver = createOpResolver();
+    tflite::InterpreterBuilder builder(*mModel, *resolver);
+
+    if (builder(&mInterpreter) != kTfLiteOk || !mInterpreter) {
+        LOG_ALWAYS_FATAL("Failed to build interpreter");
+    }
+
+    mRunner = mInterpreter->GetSignatureRunner(SIGNATURE_KEY);
+    LOG_ALWAYS_FATAL_IF(!mRunner, "Failed to find runner for signature '%s'", SIGNATURE_KEY);
+
+    allocateTensors();
+}
+
+TfLiteMotionPredictorModel::~TfLiteMotionPredictorModel() {}
+
+void TfLiteMotionPredictorModel::allocateTensors() {
+    if (mRunner->AllocateTensors() != kTfLiteOk) {
+        LOG_ALWAYS_FATAL("Failed to allocate tensors");
+    }
+
+    attachInputTensors();
+    attachOutputTensors();
+
+    checkTensor<float>(mInputR);
+    checkTensor<float>(mInputPhi);
+    checkTensor<float>(mInputPressure);
+    checkTensor<float>(mInputTilt);
+    checkTensor<float>(mInputOrientation);
+    checkTensor<float>(mOutputR);
+    checkTensor<float>(mOutputPhi);
+    checkTensor<float>(mOutputPressure);
+
+    const auto checkInputTensorSize = [this](const TfLiteTensor* tensor) {
+        const size_t size = getTensorBuffer<const float>(tensor).size();
+        LOG_ALWAYS_FATAL_IF(size != inputLength(),
+                            "Tensor '%s' length %zu does not match input length %zu", tensor->name,
+                            size, inputLength());
+    };
+
+    checkInputTensorSize(mInputR);
+    checkInputTensorSize(mInputPhi);
+    checkInputTensorSize(mInputPressure);
+    checkInputTensorSize(mInputTilt);
+    checkInputTensorSize(mInputOrientation);
+}
+
+void TfLiteMotionPredictorModel::attachInputTensors() {
+    mInputR = findInputTensor(INPUT_R, mRunner);
+    mInputPhi = findInputTensor(INPUT_PHI, mRunner);
+    mInputPressure = findInputTensor(INPUT_PRESSURE, mRunner);
+    mInputTilt = findInputTensor(INPUT_TILT, mRunner);
+    mInputOrientation = findInputTensor(INPUT_ORIENTATION, mRunner);
+}
+
+void TfLiteMotionPredictorModel::attachOutputTensors() {
+    mOutputR = findOutputTensor(OUTPUT_R, mRunner);
+    mOutputPhi = findOutputTensor(OUTPUT_PHI, mRunner);
+    mOutputPressure = findOutputTensor(OUTPUT_PRESSURE, mRunner);
+}
+
+bool TfLiteMotionPredictorModel::invoke() {
+    ATRACE_BEGIN("TfLiteMotionPredictorModel::invoke");
+    TfLiteStatus result = mRunner->Invoke();
+    ATRACE_END();
+
+    if (result != kTfLiteOk) {
+        return false;
+    }
+
+    // Invoke() might reallocate tensors, so they need to be reattached.
+    attachInputTensors();
+    attachOutputTensors();
+
+    if (outputR().size() != outputPhi().size() || outputR().size() != outputPressure().size()) {
+        LOG_ALWAYS_FATAL("Output size mismatch: (r: %zu, phi: %zu, pressure: %zu)",
+                         outputR().size(), outputPhi().size(), outputPressure().size());
+    }
+
+    return true;
+}
+
+size_t TfLiteMotionPredictorModel::inputLength() const {
+    return getTensorBuffer<const float>(mInputR).size();
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputR() {
+    return getTensorBuffer<float>(mInputR);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputPhi() {
+    return getTensorBuffer<float>(mInputPhi);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputPressure() {
+    return getTensorBuffer<float>(mInputPressure);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputTilt() {
+    return getTensorBuffer<float>(mInputTilt);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputOrientation() {
+    return getTensorBuffer<float>(mInputOrientation);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputR() const {
+    return getTensorBuffer<const float>(mOutputR);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputPhi() const {
+    return getTensorBuffer<const float>(mOutputPhi);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputPressure() const {
+    return getTensorBuffer<const float>(mOutputPressure);
+}
+
+} // namespace android
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e2c0860..37faf91 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -10,6 +10,7 @@
 
 cc_test {
     name: "libinput_tests",
+    cpp_std: "c++20",
     host_supported: true,
     srcs: [
         "IdGenerator_test.cpp",
@@ -18,20 +19,29 @@
         "InputEvent_test.cpp",
         "InputPublisherAndConsumer_test.cpp",
         "MotionPredictor_test.cpp",
+        "RingBuffer_test.cpp",
+        "TfLiteMotionPredictor_test.cpp",
         "TouchResampling_test.cpp",
         "TouchVideoFrame_test.cpp",
         "VelocityTracker_test.cpp",
         "VerifiedInputEvent_test.cpp",
     ],
+    header_libs: [
+        "flatbuffer_headers",
+        "tensorflow_headers",
+    ],
     static_libs: [
+        "libgmock",
         "libgui_window_info_static",
         "libinput",
+        "libtflite_static",
         "libui-types",
     ],
     cflags: [
         "-Wall",
         "-Wextra",
         "-Werror",
+        "-Wno-unused-parameter",
     ],
     shared_libs: [
         "libbase",
@@ -42,7 +52,10 @@
         "libutils",
         "libvintf",
     ],
-    data: ["data/*"],
+    data: [
+        "data/*",
+        ":motion_predictor_model.fb",
+    ],
     test_options: {
         unit_test: true,
     },
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index d2b59a1..ce87c86 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -14,17 +14,36 @@
  * limitations under the License.
  */
 
+#include <chrono>
+
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <gui/constants.h>
 #include <input/Input.h>
 #include <input/MotionPredictor.h>
 
+using namespace std::literals::chrono_literals;
+
 namespace android {
 
+using ::testing::IsEmpty;
+using ::testing::SizeIs;
+using ::testing::UnorderedElementsAre;
+
+const char MODEL_PATH[] =
+#if defined(__ANDROID__)
+        "/system/etc/motion_predictor_model.fb";
+#else
+        "motion_predictor_model.fb";
+#endif
+
 constexpr int32_t DOWN = AMOTION_EVENT_ACTION_DOWN;
 constexpr int32_t MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int32_t UP = AMOTION_EVENT_ACTION_UP;
+constexpr nsecs_t NSEC_PER_MSEC = 1'000'000;
 
-static MotionEvent getMotionEvent(int32_t action, float x, float y, nsecs_t eventTime) {
+static MotionEvent getMotionEvent(int32_t action, float x, float y,
+                                  std::chrono::nanoseconds eventTime, int32_t deviceId = 0) {
     MotionEvent event;
     constexpr size_t pointerCount = 1;
     std::vector<PointerProperties> pointerProperties;
@@ -33,6 +52,7 @@
         PointerProperties properties;
         properties.clear();
         properties.id = i;
+        properties.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
         pointerProperties.push_back(properties);
         PointerCoords coords;
         coords.clear();
@@ -42,73 +62,93 @@
     }
 
     ui::Transform identityTransform;
-    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_STYLUS,
-                     ADISPLAY_ID_DEFAULT, {0}, action, /*actionButton=*/0, /*flags=*/0,
-                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
-                     MotionClassification::NONE, identityTransform, /*xPrecision=*/0.1,
+    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,
+                     /*xPrecision=*/0.1,
                      /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540,
-                     identityTransform, /*downTime=*/100, eventTime, pointerCount,
+                     identityTransform, /*downTime=*/100, eventTime.count(), pointerCount,
                      pointerProperties.data(), pointerCoords.data());
     return event;
 }
 
-/**
- * A linear motion should be predicted to be linear in the future
- */
-TEST(MotionPredictorTest, LinearPrediction) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
-                              []() { return true /*enable prediction*/; });
-
-    predictor.record(getMotionEvent(DOWN, 0, 1, 0));
-    predictor.record(getMotionEvent(MOVE, 1, 3, 10));
-    predictor.record(getMotionEvent(MOVE, 2, 5, 20));
-    predictor.record(getMotionEvent(MOVE, 3, 7, 30));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
-    ASSERT_EQ(1u, predicted.size());
-    ASSERT_EQ(predicted[0]->getX(0), 4);
-    ASSERT_EQ(predicted[0]->getY(0), 9);
-}
-
-/**
- * A still motion should be predicted to remain still
- */
-TEST(MotionPredictorTest, StationaryPrediction) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
-                              []() { return true /*enable prediction*/; });
-
-    predictor.record(getMotionEvent(DOWN, 0, 1, 0));
-    predictor.record(getMotionEvent(MOVE, 0, 1, 10));
-    predictor.record(getMotionEvent(MOVE, 0, 1, 20));
-    predictor.record(getMotionEvent(MOVE, 0, 1, 30));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
-    ASSERT_EQ(1u, predicted.size());
-    ASSERT_EQ(predicted[0]->getX(0), 0);
-    ASSERT_EQ(predicted[0]->getY(0), 1);
-}
-
 TEST(MotionPredictorTest, IsPredictionAvailable) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
                               []() { return true /*enable prediction*/; });
     ASSERT_TRUE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
 }
 
 TEST(MotionPredictorTest, Offset) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1, MODEL_PATH,
                               []() { return true /*enable prediction*/; });
-    predictor.record(getMotionEvent(DOWN, 0, 1, 30));
-    predictor.record(getMotionEvent(MOVE, 0, 1, 35));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+    predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, 2, 35ms));
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
     ASSERT_EQ(1u, predicted.size());
     ASSERT_GE(predicted[0]->getEventTime(), 41);
 }
 
+TEST(MotionPredictorTest, FollowsGesture) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+                              []() { return true /*enable prediction*/; });
+
+    // MOVE without a DOWN is ignored.
+    predictor.record(getMotionEvent(MOVE, 1, 3, 10ms));
+    EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty());
+
+    predictor.record(getMotionEvent(DOWN, 2, 5, 20ms));
+    predictor.record(getMotionEvent(MOVE, 2, 7, 30ms));
+    predictor.record(getMotionEvent(MOVE, 3, 9, 40ms));
+    EXPECT_THAT(predictor.predict(50 * NSEC_PER_MSEC), SizeIs(1));
+
+    predictor.record(getMotionEvent(UP, 4, 11, 50ms));
+    EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty());
+}
+
+TEST(MotionPredictorTest, MultipleDevicesTracked) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
+                              []() { return true /*enable prediction*/; });
+
+    predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0));
+    predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0));
+    predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0));
+    predictor.record(getMotionEvent(MOVE, 3, 7, 30ms, /*deviceId=*/0));
+
+    predictor.record(getMotionEvent(DOWN, 100, 300, 0ms, /*deviceId=*/1));
+    predictor.record(getMotionEvent(MOVE, 100, 300, 10ms, /*deviceId=*/1));
+    predictor.record(getMotionEvent(MOVE, 200, 500, 20ms, /*deviceId=*/1));
+    predictor.record(getMotionEvent(MOVE, 300, 700, 30ms, /*deviceId=*/1));
+
+    {
+        std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
+        ASSERT_EQ(2u, predicted.size());
+
+        // Order of the returned vector is not guaranteed.
+        std::vector<int32_t> seenDeviceIds;
+        for (const auto& prediction : predicted) {
+            seenDeviceIds.push_back(prediction->getDeviceId());
+        }
+        EXPECT_THAT(seenDeviceIds, UnorderedElementsAre(0, 1));
+    }
+
+    // End the gesture for device 0.
+    predictor.record(getMotionEvent(UP, 4, 9, 40ms, /*deviceId=*/0));
+    predictor.record(getMotionEvent(MOVE, 400, 900, 40ms, /*deviceId=*/1));
+
+    {
+        std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
+        ASSERT_EQ(1u, predicted.size());
+        ASSERT_EQ(predicted[0]->getDeviceId(), 1);
+    }
+}
+
 TEST(MotionPredictorTest, FlagDisablesPrediction) {
-    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH,
                               []() { return false /*disable prediction*/; });
-    predictor.record(getMotionEvent(DOWN, 0, 1, 30));
-    predictor.record(getMotionEvent(MOVE, 0, 1, 35));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
+    predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, 1, 35ms));
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40 * NSEC_PER_MSEC);
     ASSERT_EQ(0u, predicted.size());
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp
new file mode 100644
index 0000000..8a6ef4c
--- /dev/null
+++ b/libs/input/tests/RingBuffer_test.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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 <algorithm>
+#include <iterator>
+#include <memory>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/RingBuffer.h>
+
+namespace android {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
+using ::testing::Not;
+using ::testing::SizeIs;
+
+TEST(RingBufferTest, PushPop) {
+    RingBuffer<int> buffer(/*capacity=*/3);
+
+    buffer.pushBack(1);
+    buffer.pushBack(2);
+    buffer.pushBack(3);
+    EXPECT_THAT(buffer, ElementsAre(1, 2, 3));
+
+    buffer.pushBack(4);
+    EXPECT_THAT(buffer, ElementsAre(2, 3, 4));
+
+    buffer.pushFront(1);
+    EXPECT_THAT(buffer, ElementsAre(1, 2, 3));
+
+    EXPECT_EQ(1, buffer.popFront());
+    EXPECT_THAT(buffer, ElementsAre(2, 3));
+
+    buffer.pushBack(4);
+    EXPECT_THAT(buffer, ElementsAre(2, 3, 4));
+
+    buffer.pushBack(5);
+    EXPECT_THAT(buffer, ElementsAre(3, 4, 5));
+
+    EXPECT_EQ(5, buffer.popBack());
+    EXPECT_THAT(buffer, ElementsAre(3, 4));
+
+    EXPECT_EQ(4, buffer.popBack());
+    EXPECT_THAT(buffer, ElementsAre(3));
+
+    EXPECT_EQ(3, buffer.popBack());
+    EXPECT_THAT(buffer, ElementsAre());
+
+    buffer.pushBack(1);
+    EXPECT_THAT(buffer, ElementsAre(1));
+
+    EXPECT_EQ(1, buffer.popFront());
+    EXPECT_THAT(buffer, ElementsAre());
+}
+
+TEST(RingBufferTest, ObjectType) {
+    RingBuffer<std::unique_ptr<int>> buffer(/*capacity=*/2);
+    buffer.pushBack(std::make_unique<int>(1));
+    buffer.pushBack(std::make_unique<int>(2));
+    buffer.pushBack(std::make_unique<int>(3));
+
+    EXPECT_EQ(2, *buffer[0]);
+    EXPECT_EQ(3, *buffer[1]);
+}
+
+TEST(RingBufferTest, ConstructConstantValue) {
+    RingBuffer<int> buffer(/*count=*/3, /*value=*/10);
+    EXPECT_THAT(buffer, ElementsAre(10, 10, 10));
+    EXPECT_EQ(3u, buffer.capacity());
+}
+
+TEST(RingBufferTest, Assignment) {
+    RingBuffer<int> a(/*capacity=*/2);
+    a.pushBack(1);
+    a.pushBack(2);
+
+    RingBuffer<int> b(/*capacity=*/3);
+    b.pushBack(10);
+    b.pushBack(20);
+    b.pushBack(30);
+
+    std::swap(a, b);
+    EXPECT_THAT(a, ElementsAre(10, 20, 30));
+    EXPECT_THAT(b, ElementsAre(1, 2));
+
+    a = b;
+    EXPECT_THAT(a, ElementsAreArray(b));
+
+    RingBuffer<int> c(b);
+    EXPECT_THAT(c, ElementsAreArray(b));
+
+    RingBuffer<int> d(std::move(b));
+    EXPECT_EQ(0u, b.capacity());
+    EXPECT_THAT(b, ElementsAre());
+    EXPECT_THAT(d, ElementsAre(1, 2));
+
+    b = std::move(d);
+    EXPECT_THAT(b, ElementsAre(1, 2));
+    EXPECT_THAT(d, ElementsAre());
+    EXPECT_EQ(0u, d.capacity());
+}
+
+TEST(RingBufferTest, Subscripting) {
+    RingBuffer<int> buffer(/*capacity=*/2);
+    buffer.pushBack(1);
+    EXPECT_EQ(1, buffer[0]);
+
+    buffer.pushFront(0);
+    EXPECT_EQ(0, buffer[0]);
+    EXPECT_EQ(1, buffer[1]);
+
+    buffer.pushFront(-1);
+    EXPECT_EQ(-1, buffer[0]);
+    EXPECT_EQ(0, buffer[1]);
+}
+
+TEST(RingBufferTest, Iterator) {
+    RingBuffer<int> buffer(/*capacity=*/3);
+    buffer.pushFront(2);
+    buffer.pushBack(3);
+
+    auto begin = buffer.begin();
+    auto end = buffer.end();
+
+    EXPECT_NE(begin, end);
+    EXPECT_LE(begin, end);
+    EXPECT_GT(end, begin);
+    EXPECT_EQ(end, begin + 2);
+    EXPECT_EQ(begin, end - 2);
+
+    EXPECT_EQ(2, end - begin);
+    EXPECT_EQ(1, end - (begin + 1));
+
+    EXPECT_EQ(2, *begin);
+    ++begin;
+    EXPECT_EQ(3, *begin);
+    --begin;
+    EXPECT_EQ(2, *begin);
+    begin += 1;
+    EXPECT_EQ(3, *begin);
+    begin += -1;
+    EXPECT_EQ(2, *begin);
+    begin -= -1;
+    EXPECT_EQ(3, *begin);
+}
+
+TEST(RingBufferTest, Clear) {
+    RingBuffer<int> buffer(/*capacity=*/2);
+    EXPECT_THAT(buffer, ElementsAre());
+
+    buffer.pushBack(1);
+    EXPECT_THAT(buffer, ElementsAre(1));
+
+    buffer.clear();
+    EXPECT_THAT(buffer, ElementsAre());
+    EXPECT_THAT(buffer, SizeIs(0));
+    EXPECT_THAT(buffer, IsEmpty());
+
+    buffer.pushFront(1);
+    EXPECT_THAT(buffer, ElementsAre(1));
+}
+
+TEST(RingBufferTest, SizeAndIsEmpty) {
+    RingBuffer<int> buffer(/*capacity=*/2);
+    EXPECT_THAT(buffer, SizeIs(0));
+    EXPECT_THAT(buffer, IsEmpty());
+
+    buffer.pushBack(1);
+    EXPECT_THAT(buffer, SizeIs(1));
+    EXPECT_THAT(buffer, Not(IsEmpty()));
+
+    buffer.pushBack(2);
+    EXPECT_THAT(buffer, SizeIs(2));
+    EXPECT_THAT(buffer, Not(IsEmpty()));
+
+    buffer.pushBack(3);
+    EXPECT_THAT(buffer, SizeIs(2));
+    EXPECT_THAT(buffer, Not(IsEmpty()));
+
+    buffer.popFront();
+    EXPECT_THAT(buffer, SizeIs(1));
+    EXPECT_THAT(buffer, Not(IsEmpty()));
+
+    buffer.popBack();
+    EXPECT_THAT(buffer, SizeIs(0));
+    EXPECT_THAT(buffer, IsEmpty());
+}
+
+} // namespace
+} // namespace android
diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp
new file mode 100644
index 0000000..454f2aa
--- /dev/null
+++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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 <algorithm>
+#include <cmath>
+#include <fstream>
+#include <ios>
+#include <iterator>
+#include <string>
+
+#include <android-base/file.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/TfLiteMotionPredictor.h>
+
+namespace android {
+namespace {
+
+using ::testing::Each;
+using ::testing::ElementsAre;
+using ::testing::FloatNear;
+
+std::string getModelPath() {
+#if defined(__ANDROID__)
+    return "/system/etc/motion_predictor_model.fb";
+#else
+    return base::GetExecutableDirectory() + "/motion_predictor_model.fb";
+#endif
+}
+
+TEST(TfLiteMotionPredictorTest, BuffersReadiness) {
+    TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5);
+    ASSERT_FALSE(buffers.isReady());
+
+    buffers.pushSample(/*timestamp=*/0, {.position = {.x = 100, .y = 100}});
+    ASSERT_FALSE(buffers.isReady());
+
+    buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 100}});
+    ASSERT_FALSE(buffers.isReady());
+
+    // Two samples with distinct positions are required.
+    buffers.pushSample(/*timestamp=*/2, {.position = {.x = 100, .y = 110}});
+    ASSERT_TRUE(buffers.isReady());
+
+    buffers.reset();
+    ASSERT_FALSE(buffers.isReady());
+}
+
+TEST(TfLiteMotionPredictorTest, BuffersRecentData) {
+    TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5);
+
+    buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}});
+    ASSERT_EQ(buffers.lastTimestamp(), 1);
+
+    buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}});
+    ASSERT_EQ(buffers.lastTimestamp(), 2);
+    ASSERT_TRUE(buffers.isReady());
+    ASSERT_EQ(buffers.axisFrom().position.x, 100);
+    ASSERT_EQ(buffers.axisFrom().position.y, 200);
+    ASSERT_EQ(buffers.axisTo().position.x, 150);
+    ASSERT_EQ(buffers.axisTo().position.y, 250);
+
+    // Position doesn't change, so neither do the axes.
+    buffers.pushSample(/*timestamp=*/3, {.position = {.x = 150, .y = 250}});
+    ASSERT_EQ(buffers.lastTimestamp(), 3);
+    ASSERT_TRUE(buffers.isReady());
+    ASSERT_EQ(buffers.axisFrom().position.x, 100);
+    ASSERT_EQ(buffers.axisFrom().position.y, 200);
+    ASSERT_EQ(buffers.axisTo().position.x, 150);
+    ASSERT_EQ(buffers.axisTo().position.y, 250);
+
+    buffers.pushSample(/*timestamp=*/4, {.position = {.x = 180, .y = 280}});
+    ASSERT_EQ(buffers.lastTimestamp(), 4);
+    ASSERT_TRUE(buffers.isReady());
+    ASSERT_EQ(buffers.axisFrom().position.x, 150);
+    ASSERT_EQ(buffers.axisFrom().position.y, 250);
+    ASSERT_EQ(buffers.axisTo().position.x, 180);
+    ASSERT_EQ(buffers.axisTo().position.y, 280);
+}
+
+TEST(TfLiteMotionPredictorTest, BuffersCopyTo) {
+    std::unique_ptr<TfLiteMotionPredictorModel> model =
+            TfLiteMotionPredictorModel::create(getModelPath().c_str());
+    TfLiteMotionPredictorBuffers buffers(model->inputLength());
+
+    buffers.pushSample(/*timestamp=*/1,
+                       {.position = {.x = 10, .y = 10},
+                        .pressure = 0,
+                        .orientation = 0,
+                        .tilt = 0.2});
+    buffers.pushSample(/*timestamp=*/2,
+                       {.position = {.x = 10, .y = 50},
+                        .pressure = 0.4,
+                        .orientation = M_PI / 4,
+                        .tilt = 0.3});
+    buffers.pushSample(/*timestamp=*/3,
+                       {.position = {.x = 30, .y = 50},
+                        .pressure = 0.5,
+                        .orientation = -M_PI / 4,
+                        .tilt = 0.4});
+    buffers.pushSample(/*timestamp=*/3,
+                       {.position = {.x = 30, .y = 60},
+                        .pressure = 0,
+                        .orientation = 0,
+                        .tilt = 0.5});
+    buffers.copyTo(*model);
+
+    const int zeroPadding = model->inputLength() - 3;
+    ASSERT_GE(zeroPadding, 0);
+
+    EXPECT_THAT(model->inputR().subspan(0, zeroPadding), Each(0));
+    EXPECT_THAT(model->inputPhi().subspan(0, zeroPadding), Each(0));
+    EXPECT_THAT(model->inputPressure().subspan(0, zeroPadding), Each(0));
+    EXPECT_THAT(model->inputTilt().subspan(0, zeroPadding), Each(0));
+    EXPECT_THAT(model->inputOrientation().subspan(0, zeroPadding), Each(0));
+
+    EXPECT_THAT(model->inputR().subspan(zeroPadding), ElementsAre(40, 20, 10));
+    EXPECT_THAT(model->inputPhi().subspan(zeroPadding), ElementsAre(0, -M_PI / 2, M_PI / 2));
+    EXPECT_THAT(model->inputPressure().subspan(zeroPadding), ElementsAre(0.4, 0.5, 0));
+    EXPECT_THAT(model->inputTilt().subspan(zeroPadding), ElementsAre(0.3, 0.4, 0.5));
+    EXPECT_THAT(model->inputOrientation().subspan(zeroPadding),
+                ElementsAre(FloatNear(-M_PI / 4, 1e-5), FloatNear(M_PI / 4, 1e-5),
+                            FloatNear(M_PI / 2, 1e-5)));
+}
+
+TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) {
+    std::unique_ptr<TfLiteMotionPredictorModel> model =
+            TfLiteMotionPredictorModel::create(getModelPath().c_str());
+    ASSERT_GT(model->inputLength(), 0u);
+
+    const int inputLength = model->inputLength();
+    ASSERT_EQ(inputLength, model->inputR().size());
+    ASSERT_EQ(inputLength, model->inputPhi().size());
+    ASSERT_EQ(inputLength, model->inputPressure().size());
+    ASSERT_EQ(inputLength, model->inputOrientation().size());
+    ASSERT_EQ(inputLength, model->inputTilt().size());
+
+    ASSERT_TRUE(model->invoke());
+
+    ASSERT_EQ(model->outputR().size(), model->outputPhi().size());
+    ASSERT_EQ(model->outputR().size(), model->outputPressure().size());
+}
+
+TEST(TfLiteMotionPredictorTest, ModelOutput) {
+    std::unique_ptr<TfLiteMotionPredictorModel> model =
+            TfLiteMotionPredictorModel::create(getModelPath().c_str());
+    TfLiteMotionPredictorBuffers buffers(model->inputLength());
+
+    buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}, .pressure = 0.2});
+    buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}, .pressure = 0.4});
+    buffers.pushSample(/*timestamp=*/3, {.position = {.x = 180, .y = 280}, .pressure = 0.6});
+    buffers.copyTo(*model);
+
+    ASSERT_TRUE(model->invoke());
+
+    // The actual model output is implementation-defined, but it should at least be non-zero and
+    // non-NaN.
+    const auto is_valid = [](float value) { return !isnan(value) && value != 0; };
+    ASSERT_TRUE(std::all_of(model->outputR().begin(), model->outputR().end(), is_valid));
+    ASSERT_TRUE(std::all_of(model->outputPhi().begin(), model->outputPhi().end(), is_valid));
+    ASSERT_TRUE(
+            std::all_of(model->outputPressure().begin(), model->outputPressure().end(), is_valid));
+}
+
+} // namespace
+} // namespace android
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 01318dc..78d1912 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -29,9 +29,9 @@
     local_include_dirs: ["include"],
 
     srcs: [
-        "recoverymap.cpp",
+        "jpegr.cpp",
         "recoverymapmath.cpp",
-        "recoverymaputils.cpp",
+        "jpegrutils.cpp",
     ],
 
     shared_libs: [
@@ -41,6 +41,8 @@
         "libjpegdecoder",
         "liblog",
     ],
+
+    static_libs: ["libskia"],
 }
 
 cc_library {
@@ -55,7 +57,7 @@
     export_include_dirs: ["include"],
 
     srcs: [
-        "jpegencoder.cpp",
+        "jpegencoderhelper.cpp",
     ],
 }
 
@@ -71,6 +73,6 @@
     export_include_dirs: ["include"],
 
     srcs: [
-        "jpegdecoder.cpp",
+        "jpegdecoderhelper.cpp",
     ],
 }
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
similarity index 88%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
index d0de48f..8748237 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2022 The Android Open Source Project
  *
@@ -15,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
 
 // We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
 #include <cstdio>
@@ -26,15 +25,15 @@
 }
 #include <utils/Errors.h>
 #include <vector>
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 /*
  * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
  * This class is not thread-safe.
  */
-class JpegDecoder {
+class JpegDecoderHelper {
 public:
-    JpegDecoder();
-    ~JpegDecoder();
+    JpegDecoderHelper();
+    ~JpegDecoderHelper();
     /*
      * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
      * calling this method, call getDecompressedImage() to get the image.
@@ -47,7 +46,7 @@
      */
     void* getDecompressedImagePtr();
     /*
-     * Returns the decompressed raw image buffer size. This mgit ethod must be called only after
+     * Returns the decompressed raw image buffer size. This method must be called only after
      * calling decompressImage().
      */
     size_t getDecompressedImageSize();
@@ -92,10 +91,6 @@
                                       size_t* pWidth, size_t* pHeight,
                                       std::vector<uint8_t>* iccData,
                                       std::vector<uint8_t>* exifData);
-    /*
-     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
-     */
-    bool extractEXIF(const void* image, int length);
 
 private:
     bool decode(const void* image, int length, bool decodeToRGBA);
@@ -120,6 +115,6 @@
     // Position of EXIF package, default value is -1 which means no EXIF package appears.
     size_t mExifPos;
 };
-} /* namespace android  */
+} /* namespace android::jpegrecoverymap  */
 
-#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
similarity index 91%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
index 61aeb8a..8b82b2b 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
-#define ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
 
 // We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
 #include <cstdio>
@@ -28,16 +28,16 @@
 #include <utils/Errors.h>
 #include <vector>
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 /*
  * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
  * This class is not thread-safe.
  */
-class JpegEncoder {
+class JpegEncoderHelper {
 public:
-    JpegEncoder();
-    ~JpegEncoder();
+    JpegEncoderHelper();
+    ~JpegEncoderHelper();
 
     /*
      * Compresses YUV420Planer image to JPEG format. After calling this method, call
@@ -90,6 +90,6 @@
     std::vector<JOCTET> mResultBuffer;
 };
 
-} /* namespace android  */
+} /* namespace android::jpegrecoverymap  */
 
-#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
similarity index 86%
rename from libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
index ae15d24..5455ba6 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
-#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGR_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGR_H
 
 #include "jpegrerrorcode.h"
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
+// Color gamuts for image data
 typedef enum {
   JPEGR_COLORGAMUT_UNSPECIFIED,
   JPEGR_COLORGAMUT_BT709,
@@ -28,12 +29,13 @@
   JPEGR_COLORGAMUT_BT2100,
 } jpegr_color_gamut;
 
-// Transfer functions as defined for XMP metadata
+// Transfer functions for image data
 typedef enum {
   JPEGR_TF_UNSPECIFIED = -1,
   JPEGR_TF_LINEAR = 0,
   JPEGR_TF_HLG = 1,
   JPEGR_TF_PQ = 2,
+  JPEGR_TF_SRGB = 3,
 } jpegr_transfer_function;
 
 struct jpegr_info_struct {
@@ -81,45 +83,13 @@
     int length;
 };
 
-struct chromaticity_coord {
-  float x;
-  float y;
-};
-
-
-struct st2086_metadata {
-  // xy chromaticity coordinate of the red primary of the mastering display
-  chromaticity_coord redPrimary;
-  // xy chromaticity coordinate of the green primary of the mastering display
-  chromaticity_coord greenPrimary;
-  // xy chromaticity coordinate of the blue primary of the mastering display
-  chromaticity_coord bluePrimary;
-  // xy chromaticity coordinate of the white point of the mastering display
-  chromaticity_coord whitePoint;
-  // Maximum luminance in nits of the mastering display
-  uint32_t maxLuminance;
-  // Minimum luminance in nits of the mastering display
-  float minLuminance;
-};
-
-struct hdr10_metadata {
-  // Mastering display color volume
-  st2086_metadata st2086Metadata;
-  // Max frame average light level in nits
-  float maxFALL;
-  // Max content light level in nits
-  float maxCLL;
-};
-
 struct jpegr_metadata {
   // JPEG/R version
   uint32_t version;
-  // Range scaling factor for the map
-  float rangeScalingFactor;
-  // The transfer function for decoding the HDR representation of the image
-  jpegr_transfer_function transferFunction;
-  // HDR10 metadata, only applicable for transferFunction of JPEGR_TF_PQ
-  hdr10_metadata hdr10Metadata;
+  // Max Content Boost for the map
+  float maxContentBoost;
+  // Min Content Boost for the map
+  float minContentBoost;
 };
 
 typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
@@ -128,7 +98,7 @@
 typedef struct jpegr_metadata* jr_metadata_ptr;
 typedef struct jpegr_info_struct* jr_info_ptr;
 
-class RecoveryMap {
+class JpegR {
 public:
     /*
      * Encode API-0
@@ -251,17 +221,7 @@
     */
     status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
                           jr_info_ptr jpegr_info);
-private:
-    /*
-     * This method is called in the encoding pipeline. It will encode the recovery map.
-     *
-     * @param uncompressed_recovery_map uncompressed recovery map
-     * @param dest encoded recover map
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
-                               jr_compressed_ptr dest);
-
+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 recovery map. The input images
@@ -269,14 +229,14 @@
      *
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param hdr_tf transfer function of the HDR image
      * @param dest recovery map; caller responsible for memory of data
-     * @param metadata metadata provides the transfer function for the HDR
-     *                 image; range_scaling_factor and hdr10 FALL and CLL will
-     *                 be updated.
+     * @param metadata max_content_boost is filled in
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
     status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                  jr_uncompressed_ptr uncompressed_p010_image,
+                                 jpegr_transfer_function hdr_tf,
                                  jr_metadata_ptr metadata,
                                  jr_uncompressed_ptr dest);
 
@@ -284,8 +244,7 @@
      * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
      * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as
      * input, and calculate the 10-bit recovered image. The recovered output image is the same
-     * color gamut as the SDR image, with the transfer function specified in the JPEG/R metadata,
-     * and is in RGBA1010102 data format.
+     * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
      *
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_recovery_map uncompressed recovery map
@@ -298,6 +257,17 @@
                               jr_metadata_ptr metadata,
                               jr_uncompressed_ptr dest);
 
+private:
+    /*
+     * This method is called in the encoding pipeline. It will encode the recovery map.
+     *
+     * @param uncompressed_recovery_map uncompressed recovery map
+     * @param dest encoded recover map
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+                               jr_compressed_ptr dest);
+
     /*
      * This methoud is called to separate primary image and recovery map image from JPEGR
      *
@@ -321,13 +291,17 @@
                                 jr_compressed_ptr dest);
 
     /*
-     * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
-     * and the compressed recovery map as input, and update the XMP metadata with the end of JPEG
-     * marker, and append the compressed gian map after the JPEG.
+     * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
+     * the compressed recovery 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, recovery map
+     * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
+     * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
+     * the input JPEG has EXIF.
      *
      * @param compressed_jpeg_image compressed 8-bit JPEG image
      * @param compress_recovery_map compressed recover map
-     * @param exif EXIF package
+     * @param (nullable) exif EXIF 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.
@@ -349,6 +323,6 @@
                      jr_uncompressed_ptr dest);
 };
 
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
 
-#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGR_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
index 699c0d3..f730343 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -16,7 +16,7 @@
 
 #include <utils/Errors.h>
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 enum {
     // status_t map for errors in the media framework
@@ -48,4 +48,4 @@
     ERROR_JPEGR_TONEMAP_ERROR           = JPEGR_RUNTIME_ERROR_BASE - 5,
 };
 
-}  // namespace android::recoverymap
+}  // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
new file mode 100644
index 0000000..3a0f67d
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
+
+#include <jpegrecoverymap/jpegr.h>
+
+#include <sstream>
+#include <stdint.h>
+#include <string>
+#include <cstdio>
+
+namespace android::jpegrecoverymap {
+
+struct jpegr_metadata;
+
+/*
+ * 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, jpegr_metadata* metadata);
+
+/*
+ * This method generates XMP metadata.
+ *
+ * below is an example of the XMP metadata that this function generates where
+ * secondary_image_length = 1000
+ * max_content_boost = 8.0
+ *
+ * <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:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/">
+ *       <Container:Directory>
+ *         <rdf:Seq>
+ *           <rdf:li>
+ *             <Container:Item
+ *              Item:Semantic="Primary"
+ *              Item:Mime="image/jpeg"
+ *              RecoveryMap:Version="1"
+ *              RecoveryMap:MaxContentBoost="8.0"/>
+ *           </rdf:li>
+ *           <rdf:li>
+ *             <Container:Item
+ *               Item:Semantic="RecoveryMap"
+ *               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
+ * @param metadata JPEG/R metadata to encode as XMP
+ * @return XMP metadata in type of string
+ */
+std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
+}  // namespace android::jpegrecoverymap
+
+#endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index 0695bb7..c12cee9 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -20,9 +20,9 @@
 #include <cmath>
 #include <stdint.h>
 
-#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/jpegr.h>
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 #define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
 
@@ -118,11 +118,12 @@
 constexpr size_t kRecoveryFactorPrecision = 10;
 constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision;
 struct RecoveryLUT {
-  RecoveryLUT(float hdrRatio) {
-    float increment = 2.0 / kRecoveryFactorNumEntries;
-    float value = -1.0f;
-    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++, value += increment) {
-      mRecoveryTable[idx] = pow(hdrRatio, value);
+  RecoveryLUT(jr_metadata_ptr metadata) {
+    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+      float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
+                     + log2(metadata->maxContentBoost) * value;
+      mRecoveryTable[idx] = exp2(logBoost);
     }
   }
 
@@ -130,10 +131,10 @@
   }
 
   float getRecoveryFactor(float recovery) {
-    uint32_t value = static_cast<uint32_t>(((recovery + 1.0f) / 2.0f) * kRecoveryFactorNumEntries);
+    uint32_t idx = static_cast<uint32_t>(recovery * (kRecoveryFactorNumEntries - 1));
     //TODO() : Remove once conversion modules have appropriate clamping in place
-    value = CLIP3(value, 0, kRecoveryFactorNumEntries - 1);
-    return mRecoveryTable[value];
+    idx = CLIP3(idx, 0, kRecoveryFactorNumEntries - 1);
+    return mRecoveryTable[idx];
   }
 
 private:
@@ -219,6 +220,9 @@
 float srgbInvOetfLUT(float e_gamma);
 Color srgbInvOetfLUT(Color e_gamma);
 
+constexpr size_t kSrgbInvOETFPrecision = 10;
+constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
+
 ////////////////////////////////////////////////////////////////////////////////
 // Display-P3 transformations
 
@@ -260,6 +264,9 @@
 float hlgOetfLUT(float e);
 Color hlgOetfLUT(Color e);
 
+constexpr size_t kHlgOETFPrecision = 10;
+constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
+
 /*
  * Convert from HLG to scene luminance.
  *
@@ -270,6 +277,9 @@
 float hlgInvOetfLUT(float e_gamma);
 Color hlgInvOetfLUT(Color e_gamma);
 
+constexpr size_t kHlgInvOETFPrecision = 10;
+constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
+
 /*
  * Convert from scene luminance to PQ.
  *
@@ -280,6 +290,9 @@
 float pqOetfLUT(float e);
 Color pqOetfLUT(Color e);
 
+constexpr size_t kPqOETFPrecision = 10;
+constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+
 /*
  * Convert from PQ to scene luminance in nits.
  *
@@ -290,6 +303,9 @@
 float pqInvOetfLUT(float e_gamma);
 Color pqInvOetfLUT(Color e_gamma);
 
+constexpr size_t kPqInvOETFPrecision = 10;
+constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
+
 
 ////////////////////////////////////////////////////////////////////////////////
 // Color space conversions
@@ -326,13 +342,13 @@
  * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR
  * luminances in linear space, and the hdr ratio to encode against.
  */
-uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio);
+uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata);
 
 /*
  * Calculates the linear luminance in nits after applying the given recovery
  * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
  */
-Color applyRecovery(Color e, float recovery, float hdr_ratio);
+Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata);
 Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT);
 
 /*
@@ -376,6 +392,6 @@
  */
 uint32_t colorToRgba1010102(Color e_gamma);
 
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
 
 #endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
deleted file mode 100644
index 8b2672f..0000000
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
+++ /dev/null
@@ -1,187 +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_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
-#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
-
-#include <jpegrecoverymap/recoverymap.h>
-
-#include <sstream>
-#include <stdint.h>
-#include <string>
-#include <cstdio>
-
-namespace android::recoverymap {
-
-struct jpegr_metadata;
-
-// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry
-// where the length is represented by this value.
-const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28;
-// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is
-// represented by this value.
-const size_t EXIF_J_R_ENTRY_LENGTH = 12;
-
-/*
- * 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);
-status_t Write(jr_exif_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, jpegr_metadata* metadata);
-
-/*
- * This method generates XMP metadata.
- *
- * below is an example of the XMP metadata that this function generates where
- * secondary_image_length = 1000
- * range_scaling_factor = 1.25
- *
- * <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:GContainer="http://ns.google.com/photos/1.0/container/"
- *       xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/">
- *       <GContainer:Version>1</GContainer:Version>
- *       <GContainer:Directory>
- *         <rdf:Seq>
- *           <rdf:li>
- *             <GContainer:Item
- *               GContainer:ItemSemantic="Primary"
- *               GContainer:ItemMime="image/jpeg"
- *               RecoveryMap:Version=”1”
- *               RecoveryMap:RangeScalingFactor=”1.25”
- *               RecoveryMap:TransferFunction=”2”/>
- *               <RecoveryMap:HDR10Metadata
- *                 // some attributes
- *                 // some elements
- *               </RecoveryMap:HDR10Metadata>
- *           </rdf:li>
- *           <rdf:li>
- *             <GContainer:Item
- *               GContainer:ItemSemantic="RecoveryMap"
- *               GContainer:ItemMime="image/jpeg"
- *               GContainer:ItemLength="1000"/>
- *           </rdf:li>
- *         </rdf:Seq>
- *       </GContainer:Directory>
- *     </rdf:Description>
- *   </rdf:RDF>
- * </x:xmpmeta>
- *
- * @param secondary_image_length length of secondary image
- * @param metadata JPEG/R metadata to encode as XMP
- * @return XMP metadata in type of string
- */
-std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
-
-/*
- * Add J R entry to existing exif, or create a new one with J R entry if it's null.
- * EXIF syntax / change:
- * ori:
- * FF E1 - APP1
- * 01 FC - size of APP1 (to be calculated)
- * -----------------------------------------------------
- * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
- * 49 49 2A 00 - TIFF Header
- * 08 00 00 00 - offset to the IFD (image file directory)
- * 06 00 - 6 entries
- * 00 01 - Width Tag
- * 03 00 - 'Short' type
- * 01 00 00 00 - 1 component
- * 00 05 00 00 - image with 0x500
- *--------------------------------------------------------------------------
- * new:
- * FF E1 - APP1
- * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12)
- *-----------------------------------------------------
- * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
- * 49 49 2A 00 - TIFF Header
- * 08 00 00 00 - offset to the IFD (image file directory)
- * 07 00 - +1 entry
- * 4A 52   Custom ('J''R') Tag
- * 07 00 - Unknown type
- * 01 00 00 00 - 1 component
- * 00 00 00 00 - empty data
- * 00 01 - Width Tag
- * 03 00 - 'Short' type
- * 01 00 00 00 - 1 component
- * 00 05 00 00 - image with 0x500
- */
-status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest);
-
-/*
- * Modify offsets in EXIF in place.
- *
- * Each tag has the following structure:
- *
- * 00 01 - Tag
- * 03 00 - data format
- * 01 00 00 00 - number of components
- * 00 05 00 00 - value
- *
- * The value means offset if
- * (1) num_of_components * bytes_per_component > 4 bytes, or
- * (2) tag == 0x8769 (ExifOffset).
- * In both cases, the method will add EXIF_J_R_ENTRY_LENGTH (12) to the offsets.
- */
-void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian);
-void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian);
-
-/*
- * Read data from the target position and target length in bytes;
- */
-int readValue(uint8_t* data, int pos, int length, bool use_big_endian);
-
-/*
- * Returns the length of data format in bytes
- *
- *  ----------------------------------------------------------------------------------------------
- *  |       value       |         1       |        2        |        3         |       4         |
- *  |       format      |  unsigned byte  |  ascii strings  |  unsigned short  |  unsigned long  |
- *  |  bytes/component  |         1       |        1        |        2         |       4         |
- *  ----------------------------------------------------------------------------------------------
- *  |       value       |         5       |        6        |        7         |       8         |
- *  |       format      |unsigned rational|   signed byte   |    undefined     |  signed short   |
- *  |  bytes/component  |         8       |        1        |        1         |       2         |
- *  ----------------------------------------------------------------------------------------------
- *  |       value       |         9       |        10       |        11        |       12        |
- *  |       format      |   signed long   | signed rational |   single float   |  double float   |
- *  |  bytes/component  |         4       |        8        |        4         |       8         |
- *  ----------------------------------------------------------------------------------------------
- */
-int findFormatLengthInBytes(int data_format);
-}
-
-#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoderhelper.cpp
similarity index 81%
rename from libs/jpegrecoverymap/jpegdecoder.cpp
rename to libs/jpegrecoverymap/jpegdecoderhelper.cpp
index 6fbc6b0..d36bbf8 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoderhelper.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/jpegdecoderhelper.h>
 
 #include <utils/Log.h>
 
@@ -24,7 +24,7 @@
 
 using namespace std;
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 const uint32_t kAPP0Marker = JPEG_APP0;      // JFIF
 const uint32_t kAPP1Marker = JPEG_APP0 + 1;  // EXIF, XMP
@@ -90,14 +90,14 @@
     longjmp(err->setjmp_buffer, 1);
 }
 
-JpegDecoder::JpegDecoder() {
+JpegDecoderHelper::JpegDecoderHelper() {
   mExifPos = 0;
 }
 
-JpegDecoder::~JpegDecoder() {
+JpegDecoderHelper::~JpegDecoderHelper() {
 }
 
-bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRGBA) {
+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;
@@ -112,39 +112,39 @@
     return true;
 }
 
-void* JpegDecoder::getDecompressedImagePtr() {
+void* JpegDecoderHelper::getDecompressedImagePtr() {
     return mResultBuffer.data();
 }
 
-size_t JpegDecoder::getDecompressedImageSize() {
+size_t JpegDecoderHelper::getDecompressedImageSize() {
     return mResultBuffer.size();
 }
 
-void* JpegDecoder::getXMPPtr() {
+void* JpegDecoderHelper::getXMPPtr() {
     return mXMPBuffer.data();
 }
 
-size_t JpegDecoder::getXMPSize() {
+size_t JpegDecoderHelper::getXMPSize() {
     return mXMPBuffer.size();
 }
 
-void* JpegDecoder::getEXIFPtr() {
+void* JpegDecoderHelper::getEXIFPtr() {
     return mEXIFBuffer.data();
 }
 
-size_t JpegDecoder::getEXIFSize() {
+size_t JpegDecoderHelper::getEXIFSize() {
     return mEXIFBuffer.size();
 }
 
-size_t JpegDecoder::getDecompressedImageWidth() {
+size_t JpegDecoderHelper::getDecompressedImageWidth() {
     return mWidth;
 }
 
-size_t JpegDecoder::getDecompressedImageHeight() {
+size_t JpegDecoderHelper::getDecompressedImageHeight() {
     return mHeight;
 }
 
-bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) {
+bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
@@ -248,61 +248,7 @@
     return true;
 }
 
-// TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have
-// similar functionality. Yet Dichen is not familiar with who's calling
-// getCompressedImageParameters(), looks like it's used by some pending CLs.
-bool JpegDecoder::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);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
-
-    cinfo.src = &mgr;
-    jpeg_read_header(&cinfo, TRUE);
-
-    bool exifAppears = false;
-    size_t pos = 2;  // position after SOI
-    for (jpeg_marker_struct* marker = cinfo.marker_list;
-         marker && !exifAppears;
-         marker = marker->next) {
-
-        pos += 4;
-        pos += marker->original_length;
-
-        if (marker->marker != kAPP1Marker) {
-            continue;
-        }
-
-        const unsigned int len = marker->data_length;
-        if (!exifAppears &&
-            len > kExifIdCode.size() &&
-            !strncmp(reinterpret_cast<const char*>(marker->data),
-                     kExifIdCode.c_str(),
-                     kExifIdCode.size())) {
-            mEXIFBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
-            exifAppears = true;
-            mExifPos = pos - marker->original_length;
-        }
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    return true;
-}
-
-bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
+bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
         bool isSingleChannel) {
     if (isSingleChannel) {
         return decompressSingleChannel(cinfo, dest);
@@ -313,7 +259,7 @@
         return decompressYUV(cinfo, dest);
 }
 
-bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
+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;
@@ -380,7 +326,7 @@
     return true;
 }
 
-bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
     JSAMPLE* decodeDst = (JSAMPLE*) dest;
     uint32_t lines = 0;
     // TODO: use batches for more effectiveness
@@ -395,7 +341,7 @@
     return lines == cinfo->image_height;
 }
 
-bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
 
     JSAMPROW y[kCompressBatchSize];
     JSAMPROW cb[kCompressBatchSize / 2];
@@ -440,7 +386,7 @@
     return true;
 }
 
-bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
     JSAMPROW y[kCompressBatchSize];
     JSAMPARRAY planes[1] {y};
 
@@ -467,4 +413,4 @@
     return true;
 }
 
-} // namespace android
+} // namespace jpegrecoverymap
diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoderhelper.cpp
similarity index 86%
rename from libs/jpegrecoverymap/jpegencoder.cpp
rename to libs/jpegrecoverymap/jpegencoderhelper.cpp
index 627dcdf..586cd34 100644
--- a/libs/jpegrecoverymap/jpegencoder.cpp
+++ b/libs/jpegrecoverymap/jpegencoderhelper.cpp
@@ -14,28 +14,28 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegencoder.h>
+#include <jpegrecoverymap/jpegencoderhelper.h>
 
 #include <utils/Log.h>
 
 #include <errno.h>
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
-// The destination manager that can access |mResultBuffer| in JpegEncoder.
+// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
 struct destination_mgr {
 public:
     struct jpeg_destination_mgr mgr;
-    JpegEncoder* encoder;
+    JpegEncoderHelper* encoder;
 };
 
-JpegEncoder::JpegEncoder() {
+JpegEncoderHelper::JpegEncoderHelper() {
 }
 
-JpegEncoder::~JpegEncoder() {
+JpegEncoderHelper::~JpegEncoderHelper() {
 }
 
-bool JpegEncoder::compressImage(const void* image, int width, int height, int quality,
+bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality,
                                    const void* iccBuffer, unsigned int iccSize,
                                    bool isSingleChannel) {
     if (width % 8 != 0 || height % 2 != 0) {
@@ -52,15 +52,15 @@
     return true;
 }
 
-void* JpegEncoder::getCompressedImagePtr() {
+void* JpegEncoderHelper::getCompressedImagePtr() {
     return mResultBuffer.data();
 }
 
-size_t JpegEncoder::getCompressedImageSize() {
+size_t JpegEncoderHelper::getCompressedImageSize() {
     return mResultBuffer.size();
 }
 
-void JpegEncoder::initDestination(j_compress_ptr cinfo) {
+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);
@@ -68,7 +68,7 @@
     dest->mgr.free_in_buffer = buffer.size();
 }
 
-boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) {
+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();
@@ -78,13 +78,13 @@
     return true;
 }
 
-void JpegEncoder::terminateDestination(j_compress_ptr cinfo) {
+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 JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
+void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
     char buffer[JMSG_LENGTH_MAX];
 
     /* Create the message */
@@ -92,7 +92,7 @@
     ALOGE("%s\n", buffer);
 }
 
-bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuality,
+bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality,
                          const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
     jpeg_compress_struct cinfo;
     jpeg_error_mgr jerr;
@@ -118,7 +118,7 @@
     return true;
 }
 
-void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
+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;
@@ -128,7 +128,7 @@
     cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
 }
 
-void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
+void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
                                         jpeg_compress_struct* cinfo, bool isSingleChannel) {
     cinfo->image_width = width;
     cinfo->image_height = height;
@@ -158,7 +158,7 @@
     }
 }
 
-bool JpegEncoder::compress(
+bool JpegEncoderHelper::compress(
         jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
     if (isSingleChannel) {
         return compressSingleChannel(cinfo, image);
@@ -166,7 +166,7 @@
     return compressYuv(cinfo, image);
 }
 
-bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
     JSAMPROW y[kCompressBatchSize];
     JSAMPROW cb[kCompressBatchSize / 2];
     JSAMPROW cr[kCompressBatchSize / 2];
@@ -210,7 +210,7 @@
     return true;
 }
 
-bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
+bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
     JSAMPROW y[kCompressBatchSize];
     JSAMPARRAY planes[1] {y};
 
@@ -236,4 +236,4 @@
     return true;
 }
 
-} // namespace android
+} // namespace jpegrecoverymap
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/jpegr.cpp
similarity index 75%
rename from libs/jpegrecoverymap/recoverymap.cpp
rename to libs/jpegrecoverymap/jpegr.cpp
index 22289de..bd8874e 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/jpegr.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/recoverymap.h>
-#include <jpegrecoverymap/jpegencoder.h>
-#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/jpegr.h>
+#include <jpegrecoverymap/jpegencoderhelper.h>
+#include <jpegrecoverymap/jpegdecoderhelper.h>
 #include <jpegrecoverymap/recoverymapmath.h>
-#include <jpegrecoverymap/recoverymaputils.h>
+#include <jpegrecoverymap/jpegrutils.h>
 
 #include <image_io/jpeg/jpeg_marker.h>
 #include <image_io/jpeg/jpeg_info.h>
@@ -26,7 +26,10 @@
 #include <image_io/jpeg/jpeg_info_builder.h>
 #include <image_io/base/data_segment_data_source.h>
 #include <utils/Log.h>
+#include "SkColorSpace.h"
+#include "SkICC.h"
 
+#include <map>
 #include <memory>
 #include <sstream>
 #include <string>
@@ -40,7 +43,7 @@
 using namespace std;
 using namespace photos_editing_formats::image_io;
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 #define USE_SRGB_INVOETF_LUT 1
 #define USE_HLG_OETF_LUT 1
@@ -69,16 +72,6 @@
 // JPEG compress quality (0 ~ 100) for recovery map
 static const int kMapCompressQuality = 85;
 
-// TODO: fill in st2086 metadata
-static const st2086_metadata kSt2086Metadata = {
-  {0.0f, 0.0f},
-  {0.0f, 0.0f},
-  {0.0f, 0.0f},
-  {0.0f, 0.0f},
-  0,
-  1.0f,
-};
-
 #define CONFIG_MULTITHREAD 1
 int GetCPUCoreCount() {
   int cpuCoreCount = 1;
@@ -93,33 +86,23 @@
   return cpuCoreCount;
 }
 
-/*
- * Helper function copies the JPEG image from without EXIF.
- *
- * @param dest destination of the data to be written.
- * @param source source of data being written.
- * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
- *                 (4 bypes 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().
- */
-void copyJpegWithoutExif(jr_compressed_ptr dest,
-                         jr_compressed_ptr source,
-                         size_t exif_pos,
-                         size_t exif_size) {
-  memcpy(dest, source, sizeof(jpegr_compressed_struct));
+static const map<jpegrecoverymap::jpegr_color_gamut, skcms_Matrix3x3> jrGamut_to_skGamut {
+    {JPEGR_COLORGAMUT_BT709,     SkNamedGamut::kSRGB},
+    {JPEGR_COLORGAMUT_P3,        SkNamedGamut::kDisplayP3},
+    {JPEGR_COLORGAMUT_BT2100,    SkNamedGamut::kRec2020},
+};
 
-  const size_t exif_offset = 4; //exif_pos has 4 bypes offset to the FF sign
-  dest->length = source->length - exif_size - exif_offset;
-  dest->data = malloc(dest->length);
-
-  memcpy(dest->data, source->data, exif_pos - exif_offset);
-  memcpy((uint8_t*)dest->data + exif_pos - exif_offset,
-         (uint8_t*)source->data + exif_pos + exif_size,
-         source->length - exif_pos - exif_size);
-}
+static const map<
+        jpegrecoverymap::jpegr_transfer_function,
+        skcms_TransferFunction> jrTransFunc_to_skTransFunc {
+    {JPEGR_TF_SRGB,        SkNamedTransferFn::kSRGB},
+    {JPEGR_TF_LINEAR,      SkNamedTransferFn::kLinear},
+    {JPEGR_TF_HLG,         SkNamedTransferFn::kHLG},
+    {JPEGR_TF_PQ,          SkNamedTransferFn::kPQ},
+};
 
 /* Encode API-0 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest,
                                   int quality,
@@ -141,10 +124,6 @@
 
   jpegr_metadata metadata;
   metadata.version = kJpegrVersion;
-  metadata.transferFunction = hdr_tf;
-  if (hdr_tf == JPEGR_TF_PQ) {
-    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
-  }
 
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
   unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
@@ -154,7 +133,7 @@
 
   jpegr_uncompressed_struct map;
   JPEGR_CHECK(generateRecoveryMap(
-      &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+      &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
@@ -164,35 +143,28 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JpegEncoder jpeg_encoder;
-  // TODO: determine ICC data based on color gamut information
+  sk_sp<SkData> icc = SkWriteICCProfile(
+          jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
+          jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut));
+
+  JpegEncoderHelper jpeg_encoder;
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
                                   uncompressed_yuv_420_image.width,
-                                  uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
+                                  uncompressed_yuv_420_image.height, quality,
+                                  icc.get()->data(), icc.get()->size())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
   jpegr_compressed_struct jpeg;
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  jpegr_exif_struct new_exif;
-  if (exif == nullptr || exif->data == nullptr) {
-      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
-  } else {
-      new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
-  }
-  new_exif.data = new uint8_t[new_exif.length];
-  std::unique_ptr<uint8_t[]> new_exif_data;
-  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
-  JPEGR_CHECK(updateExif(exif, &new_exif));
-
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
 
   return NO_ERROR;
 }
 
 /* Encode API-1 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
                                   jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest,
@@ -222,14 +194,10 @@
 
   jpegr_metadata metadata;
   metadata.version = kJpegrVersion;
-  metadata.transferFunction = hdr_tf;
-  if (hdr_tf == JPEGR_TF_PQ) {
-    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
-  }
 
   jpegr_uncompressed_struct map;
   JPEGR_CHECK(generateRecoveryMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+      uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
@@ -239,36 +207,28 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JpegEncoder jpeg_encoder;
-  // TODO: determine ICC data based on color gamut information
+  sk_sp<SkData> icc = SkWriteICCProfile(
+          jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
+          jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut));
+
+  JpegEncoderHelper jpeg_encoder;
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
                                   uncompressed_yuv_420_image->width,
-                                  uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
+                                  uncompressed_yuv_420_image->height, quality,
+                                  icc.get()->data(), icc.get()->size())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
   jpegr_compressed_struct jpeg;
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  jpegr_exif_struct new_exif;
-  if (exif == nullptr || exif->data == nullptr) {
-      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
-  } else {
-      new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
-  }
-
-  new_exif.data = new uint8_t[new_exif.length];
-  std::unique_ptr<uint8_t[]> new_exif_data;
-  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
-  JPEGR_CHECK(updateExif(exif, &new_exif));
-
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
 
   return NO_ERROR;
 }
 
 /* Encode API-2 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
                                   jr_compressed_ptr compressed_jpeg_image,
                                   jpegr_transfer_function hdr_tf,
@@ -294,14 +254,10 @@
 
   jpegr_metadata metadata;
   metadata.version = kJpegrVersion;
-  metadata.transferFunction = hdr_tf;
-  if (hdr_tf == JPEGR_TF_PQ) {
-    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
-  }
 
   jpegr_uncompressed_struct map;
   JPEGR_CHECK(generateRecoveryMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+      uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
@@ -311,53 +267,13 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  // Extract EXIF from JPEG without decoding.
-  JpegDecoder jpeg_decoder;
-  if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  // Update exif.
-  jpegr_exif_struct exif;
-  exif.data = nullptr;
-  exif.length = 0;
-  jpegr_compressed_struct new_jpeg_image;
-  new_jpeg_image.data = nullptr;
-  new_jpeg_image.length = 0;
-  if (jpeg_decoder.getEXIFPos() != 0) {
-    copyJpegWithoutExif(&new_jpeg_image,
-                        compressed_jpeg_image,
-                        jpeg_decoder.getEXIFPos(),
-                        jpeg_decoder.getEXIFSize());
-    exif.data = jpeg_decoder.getEXIFPtr();
-    exif.length = jpeg_decoder.getEXIFSize();
-  }
-
-  jpegr_exif_struct new_exif;
-  if (exif.data == nullptr) {
-      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
-  } else {
-      new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
-  }
-
-  new_exif.data = new uint8_t[new_exif.length];
-  std::unique_ptr<uint8_t[]> new_exif_data;
-  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
-  JPEGR_CHECK(updateExif(&exif, &new_exif));
-
-  JPEGR_CHECK(appendRecoveryMap(
-          new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
-          &compressed_map, &new_exif, &metadata, dest));
-
-  if (new_jpeg_image.data != nullptr) {
-    free(new_jpeg_image.data);
-  }
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
 
   return NO_ERROR;
 }
 
 /* Encode API-3 */
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_compressed_ptr compressed_jpeg_image,
                                   jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest) {
@@ -374,7 +290,7 @@
     return ERROR_JPEGR_INVALID_INPUT_TYPE;
   }
 
-  JpegDecoder jpeg_decoder;
+  JpegDecoderHelper jpeg_decoder;
   if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
@@ -384,33 +300,6 @@
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
   uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
 
-  // Update exif.
-  jpegr_exif_struct exif;
-  exif.data = nullptr;
-  exif.length = 0;
-  jpegr_compressed_struct new_jpeg_image;
-  new_jpeg_image.data = nullptr;
-  new_jpeg_image.length = 0;
-  if (jpeg_decoder.getEXIFPos() != 0) {
-    copyJpegWithoutExif(&new_jpeg_image,
-                        compressed_jpeg_image,
-                        jpeg_decoder.getEXIFPos(),
-                        jpeg_decoder.getEXIFSize());
-    exif.data = jpeg_decoder.getEXIFPtr();
-    exif.length = jpeg_decoder.getEXIFSize();
-  }
-
-  jpegr_exif_struct new_exif;
-  if (exif.data == nullptr) {
-      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
-  } else {
-      new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
-  }
-  new_exif.data = new uint8_t[new_exif.length];
-  std::unique_ptr<uint8_t[]> new_exif_data;
-  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
-  JPEGR_CHECK(updateExif(&exif, &new_exif));
-
   if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
    || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
@@ -418,14 +307,10 @@
 
   jpegr_metadata metadata;
   metadata.version = kJpegrVersion;
-  metadata.transferFunction = hdr_tf;
-  if (hdr_tf == JPEGR_TF_PQ) {
-    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
-  }
 
   jpegr_uncompressed_struct map;
   JPEGR_CHECK(generateRecoveryMap(
-      &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+      &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
@@ -435,18 +320,12 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(
-          new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
-          &compressed_map, &new_exif, &metadata, dest));
-
-  if (new_jpeg_image.data != nullptr) {
-    free(new_jpeg_image.data);
-  }
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
 
   return NO_ERROR;
 }
 
-status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
                                    jr_info_ptr jpegr_info) {
   if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -456,7 +335,7 @@
   JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
                                                 &primary_image, &recovery_map));
 
-  JpegDecoder jpeg_decoder;
+  JpegDecoderHelper jpeg_decoder;
   if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
                                                  &jpegr_info->width, &jpegr_info->height,
                                                  jpegr_info->iccData, jpegr_info->exifData)) {
@@ -467,7 +346,7 @@
 }
 
 /* Decode API */
-status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
+status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
                                   jr_uncompressed_ptr dest,
                                   jr_exif_ptr exif,
                                   bool request_sdr) {
@@ -478,7 +357,7 @@
   (void) exif;
 
   if (request_sdr) {
-    JpegDecoder jpeg_decoder;
+    JpegDecoderHelper jpeg_decoder;
     if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
                                       true)) {
         return ERROR_JPEGR_DECODE_ERROR;
@@ -498,12 +377,12 @@
   jpegr_metadata metadata;
   JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
 
-  JpegDecoder jpeg_decoder;
+  JpegDecoderHelper jpeg_decoder;
   if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
-  JpegDecoder recovery_map_decoder;
+  JpegDecoderHelper recovery_map_decoder;
   if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
@@ -527,14 +406,13 @@
   return NO_ERROR;
 }
 
-status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
                                           jr_compressed_ptr dest) {
   if (uncompressed_recovery_map == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  // TODO: should we have ICC data for the map?
-  JpegEncoder jpeg_encoder;
+  JpegEncoderHelper jpeg_encoder;
   if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
                                   uncompressed_recovery_map->width,
                                   uncompressed_recovery_map->height,
@@ -612,8 +490,9 @@
   mQueuedAllJobs = false;
 }
 
-status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                           jr_uncompressed_ptr uncompressed_p010_image,
+                                          jpegr_transfer_function hdr_tf,
                                           jr_metadata_ptr metadata,
                                           jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
@@ -650,7 +529,7 @@
 
   ColorTransformFn hdrInvOetf = nullptr;
   float hdr_white_nits = 0.0f;
-  switch (metadata->transferFunction) {
+  switch (hdr_tf) {
     case JPEGR_TF_LINEAR:
       hdrInvOetf = identityConversion;
       break;
@@ -670,7 +549,7 @@
 #endif
       hdr_white_nits = kPqMaxNits;
       break;
-    case JPEGR_TF_UNSPECIFIED:
+    default:
       // Should be impossible to hit after input validation.
       return ERROR_JPEGR_INVALID_TRANS_FUNC;
   }
@@ -695,19 +574,20 @@
   }
 
   std::mutex mutex;
-  float hdr_y_nits_max = 0.0f;
-  double hdr_y_nits_avg = 0.0f;
+  float max_gain = 0.0f;
+  float min_gain = 1.0f;
   const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
   size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
   JobQueue jobQueue;
 
-  std::function<void()> computeMetadata = [uncompressed_p010_image, hdrInvOetf,
-                                           hdrGamutConversionFn, luminanceFn, hdr_white_nits,
-                                           threads, &mutex, &hdr_y_nits_avg,
-                                           &hdr_y_nits_max, &jobQueue]() -> void {
+  std::function<void()> computeMetadata = [uncompressed_p010_image, uncompressed_yuv_420_image,
+                                           hdrInvOetf, hdrGamutConversionFn, luminanceFn,
+                                           hdr_white_nits, threads, &mutex, &max_gain, &min_gain,
+                                           &jobQueue]() -> void {
     size_t rowStart, rowEnd;
-    float hdr_y_nits_max_th = 0.0f;
-    double hdr_y_nits_avg_th = 0.0f;
+    float max_gain_th = 0.0f;
+    float min_gain_th = 1.0f;
+
     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
       for (size_t y = rowStart; y < rowEnd; ++y) {
         for (size_t x = 0; x < uncompressed_p010_image->width; ++x) {
@@ -717,25 +597,36 @@
           hdr_rgb = hdrGamutConversionFn(hdr_rgb);
           float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
 
-          hdr_y_nits_avg_th += hdr_y_nits;
-          if (hdr_y_nits > hdr_y_nits_max_th) {
-            hdr_y_nits_max_th = hdr_y_nits;
-          }
+          Color sdr_yuv_gamma =
+              getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+          Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+#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;
+
+          float gain = hdr_y_nits / sdr_y_nits;
+          max_gain_th = std::max(max_gain_th, gain);
+          min_gain_th = std::min(min_gain_th, gain);
         }
       }
     }
     std::unique_lock<std::mutex> lock{mutex};
-    hdr_y_nits_avg += hdr_y_nits_avg_th;
-    hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th);
+    max_gain = std::max(max_gain, max_gain_th);
+    min_gain = std::min(min_gain, min_gain_th);
   };
 
   std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
                                        metadata, dest, hdrInvOetf, hdrGamutConversionFn,
                                        luminanceFn, hdr_white_nits, &jobQueue]() -> void {
     size_t rowStart, rowEnd;
+    size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
+    size_t dest_map_stride = dest->width;
     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
       for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < dest->width; ++x) {
+        for (size_t x = 0; x < dest_map_width; ++x) {
           Color sdr_yuv_gamma =
               sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
           Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
@@ -752,9 +643,9 @@
           hdr_rgb = hdrGamutConversionFn(hdr_rgb);
           float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
 
-          size_t pixel_idx = x + y * dest->width;
+          size_t pixel_idx = x + y * dest_map_stride;
           reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-              encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
+              encodeRecovery(sdr_y_nits, hdr_y_nits, metadata);
         }
       }
     }
@@ -775,13 +666,9 @@
   computeMetadata();
   std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
   workers.clear();
-  hdr_y_nits_avg /= image_width * image_height;
 
-  metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
-  if (metadata->transferFunction == JPEGR_TF_PQ) {
-    metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
-    metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
-  }
+  metadata->maxContentBoost = max_gain;
+  metadata->minContentBoost = min_gain;
 
   // generate map
   jobQueue.reset();
@@ -803,7 +690,7 @@
   return NO_ERROR;
 }
 
-status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                        jr_uncompressed_ptr uncompressed_recovery_map,
                                        jr_metadata_ptr metadata,
                                        jr_uncompressed_ptr dest) {
@@ -817,39 +704,21 @@
   dest->width = uncompressed_yuv_420_image->width;
   dest->height = uncompressed_yuv_420_image->height;
   ShepardsIDW idwTable(kMapDimensionScaleFactor);
-  RecoveryLUT recoveryLUT(metadata->rangeScalingFactor);
+  RecoveryLUT recoveryLUT(metadata);
 
   JobQueue jobQueue;
   std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
                                        metadata, dest, &jobQueue, &idwTable,
                                        &recoveryLUT]() -> void {
-    const float hdr_ratio = metadata->rangeScalingFactor;
+    const float hdr_ratio = metadata->maxContentBoost;
     size_t width = uncompressed_yuv_420_image->width;
     size_t height = uncompressed_yuv_420_image->height;
 
-    ColorTransformFn hdrOetf = nullptr;
-    switch (metadata->transferFunction) {
-      case JPEGR_TF_LINEAR:
-        hdrOetf = identityConversion;
-        break;
-      case JPEGR_TF_HLG:
 #if USE_HLG_OETF_LUT
-        hdrOetf = hlgOetfLUT;
+    ColorTransformFn hdrOetf = hlgOetfLUT;
 #else
-        hdrOetf = hlgOetf;
+    ColorTransformFn hdrOetf = hlgOetf;
 #endif
-        break;
-      case JPEGR_TF_PQ:
-#if USE_PQ_OETF_LUT
-        hdrOetf = pqOetfLUT;
-#else
-        hdrOetf = pqOetf;
-#endif
-        break;
-      case JPEGR_TF_UNSPECIFIED:
-        // Should be impossible to hit after input validation.
-        hdrOetf = identityConversion;
-    }
 
     size_t rowStart, rowEnd;
     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
@@ -871,15 +740,14 @@
           if (map_scale_factor != floorf(map_scale_factor)) {
             recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
           } else {
-            recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y,
-                                idwTable);
+            recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable);
           }
 #if USE_APPLY_RECOVERY_LUT
           Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
 #else
-          Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+          Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata);
 #endif
-          Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
+          Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->maxContentBoost);
           uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
 
           size_t pixel_idx = x + y * width;
@@ -906,9 +774,9 @@
   return NO_ERROR;
 }
 
-status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                               jr_compressed_ptr primary_image,
-                                               jr_compressed_ptr recovery_map) {
+status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                                        jr_compressed_ptr primary_image,
+                                                        jr_compressed_ptr recovery_map) {
   if (compressed_jpegr_image == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -956,7 +824,7 @@
 }
 
 
-status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
                                          jr_compressed_ptr dest) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -967,29 +835,33 @@
 
 // JPEG/R structure:
 // SOI (ff d8)
+//
+// (Optional, only if EXIF package is from outside)
 // APP1 (ff e1)
 // 2 bytes of length (2 + length of exif package)
 // EXIF package (this includes the first two bytes representing the package length)
-// APP1 (ff e1)
+//
+// (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
-// primary image (without the first two bytes (SOI) and without EXIF, may have other packages)
-// secondary image (the recovery map)
+//
+// (Required) primary image (without the first two bytes (SOI), may have other packages)
+//
+// (Required) secondary image (the recovery map)
 //
 // 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 RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
+status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
                                         jr_compressed_ptr compressed_recovery_map,
                                         jr_exif_ptr exif,
                                         jr_metadata_ptr metadata,
                                         jr_compressed_ptr dest) {
   if (compressed_jpeg_image == nullptr
    || compressed_recovery_map == nullptr
-   || exif == nullptr
    || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -1002,7 +874,7 @@
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
 
   // Write EXIF
-  {
+  if (exif != nullptr) {
     const int length = 2 + exif->length;
     const uint8_t lengthH = ((length >> 8) & 0xff);
     const uint8_t lengthL = (length & 0xff);
@@ -1046,7 +918,7 @@
   return NO_ERROR;
 }
 
-status_t RecoveryMap::toneMap(jr_uncompressed_ptr src,
+status_t JpegR::toneMap(jr_uncompressed_ptr src,
                               jr_uncompressed_ptr dest) {
   if (src == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -1083,4 +955,4 @@
   return NO_ERROR;
 }
 
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp
new file mode 100644
index 0000000..bcca91a
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegrutils.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jpegrecoverymap/jpegrutils.h>
+#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>
+
+using namespace photos_editing_formats::image_io;
+using namespace std;
+
+namespace android::jpegrecoverymap {
+
+/*
+ * 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".
+ */
+string Name(const string &prefix, const string &suffix) {
+  std::stringstream ss;
+  ss << prefix << ":" << suffix;
+  return ss.str();
+}
+
+/*
+ * 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() {
+        gContainerItemState = NotStrarted;
+    }
+
+    enum ParseState {
+        NotStrarted,
+        Started,
+        Done
+    };
+
+    virtual DataMatchResult StartElement(const XmlTokenContext& context) {
+        string val;
+        if (context.BuildTokenValue(&val)) {
+            if (!val.compare(gContainerItemName)) {
+                gContainerItemState = Started;
+            } else {
+                if (gContainerItemState != Done) {
+                    gContainerItemState = NotStrarted;
+                }
+            }
+        }
+        return context.GetResult();
+    }
+
+    virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
+        if (gContainerItemState == Started) {
+            gContainerItemState = Done;
+            lastAttributeName = "";
+        }
+        return context.GetResult();
+    }
+
+    virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
+        string val;
+        if (gContainerItemState == Started) {
+            if (context.BuildTokenValue(&val)) {
+                if (!val.compare(maxContentBoostAttrName)) {
+                    lastAttributeName = maxContentBoostAttrName;
+                } else {
+                    lastAttributeName = "";
+                }
+            }
+        }
+        return context.GetResult();
+    }
+
+    virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
+        string val;
+        if (gContainerItemState == Started) {
+            if (context.BuildTokenValue(&val, true)) {
+                if (!lastAttributeName.compare(maxContentBoostAttrName)) {
+                    maxContentBoostStr = val;
+                }
+            }
+        }
+        return context.GetResult();
+    }
+
+    bool getMaxContentBoost(float* max_content_boost) {
+        if (gContainerItemState == Done) {
+            stringstream ss(maxContentBoostStr);
+            float val;
+            if (ss >> val) {
+                *max_content_boost = val;
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+private:
+    static const string gContainerItemName;
+    static const string maxContentBoostAttrName;
+    string              maxContentBoostStr;
+    string              lastAttributeName;
+    ParseState          gContainerItemState;
+};
+
+// 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::gContainerItemName = kConItem;
+
+// 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 kSemanticRecoveryMap = "RecoveryMap";
+const string kMimeImageJpeg       = "image/jpeg";
+
+// RecoveryMap XMP constants - URI and namespace prefix
+const string kRecoveryMapUri      = "http://ns.google.com/photos/1.0/recoverymap/";
+const string kRecoveryMapPrefix   = "RecoveryMap";
+
+// RecoveryMap XMP constants - element and attribute names
+const string kMapMaxContentBoost  = Name(kRecoveryMapPrefix, "MaxContentBoost");
+const string kMapVersion          = Name(kRecoveryMapPrefix, "Version");
+
+// RecoveryMap XMP constants - names for XMP handlers
+const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost;
+
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
+    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+
+    if (xmp_size < nameSpace.size()+2) {
+        // Data too short
+        return false;
+    }
+
+    if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
+        // Not correct namespace
+        return false;
+    }
+
+    // Position the pointers to the start of XMP XML portion
+    xmp_data += nameSpace.size()+1;
+    xmp_size -= nameSpace.size()+1;
+    XMPXmlHandler handler;
+
+    // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
+    while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
+        xmp_size--;
+    }
+
+    string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
+    MessageHandler msg_handler;
+    unique_ptr<XmlRule> rule(new XmlElementRule);
+    XmlReader reader(&handler, &msg_handler);
+    reader.StartParse(std::move(rule));
+    reader.Parse(str);
+    reader.FinishParse();
+    if (reader.HasErrors()) {
+        // Parse error
+        return false;
+    }
+
+    if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) {
+        return false;
+    }
+
+    return true;
+}
+
+string generateXmp(int secondary_image_length, jpegr_metadata& 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(kRecoveryMapPrefix, kRecoveryMapUri);
+  writer.StartWritingElements(kConDirSeq);
+  size_t item_depth = writer.StartWritingElements(kLiItem);
+  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
+  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
+  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
+  writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost);
+  writer.FinishWritingElementsToDepth(item_depth);
+  writer.StartWritingElements(kLiItem);
+  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
+  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
+  writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+  writer.FinishWriting();
+
+  return ss.str();
+}
+
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 4f21ac6..7812e18 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -18,67 +18,48 @@
 #include <vector>
 #include <jpegrecoverymap/recoverymapmath.h>
 
-namespace android::recoverymap {
-
-constexpr size_t kPqOETFPrecision = 10;
-constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+namespace android::jpegrecoverymap {
 
 static const std::vector<float> kPqOETF = [] {
     std::vector<float> result;
-    float increment = 1.0 / kPqOETFNumEntries;
-    float value = 0.0f;
-    for (int idx = 0; idx < kPqOETFNumEntries; idx++, value += increment) {
+    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;
 }();
 
-constexpr size_t kPqInvOETFPrecision = 10;
-constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
-
 static const std::vector<float> kPqInvOETF = [] {
     std::vector<float> result;
-    float increment = 1.0 / kPqInvOETFNumEntries;
-    float value = 0.0f;
-    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++, value += increment) {
+    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;
 }();
 
-constexpr size_t kHlgOETFPrecision = 10;
-constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
-
 static const std::vector<float> kHlgOETF = [] {
     std::vector<float> result;
-    float increment = 1.0 / kHlgOETFNumEntries;
-    float value = 0.0f;
-    for (int idx = 0; idx < kHlgOETFNumEntries; idx++, value += increment) {
+    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;
 }();
 
-constexpr size_t kHlgInvOETFPrecision = 10;
-constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
-
 static const std::vector<float> kHlgInvOETF = [] {
     std::vector<float> result;
-    float increment = 1.0 / kHlgInvOETFNumEntries;
-    float value = 0.0f;
-    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++, value += increment) {
+    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;
 }();
 
-constexpr size_t kSRGBInvOETFPrecision = 10;
-constexpr size_t kSRGBInvOETFNumEntries = 1 << kSRGBInvOETFPrecision;
-static const std::vector<float> kSRGBInvOETF = [] {
+static const std::vector<float> kSrgbInvOETF = [] {
     std::vector<float> result;
-    float increment = 1.0 / kSRGBInvOETFNumEntries;
-    float value = 0.0f;
-    for (int idx = 0; idx < kSRGBInvOETFNumEntries; idx++, value += increment) {
+    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;
@@ -182,10 +163,10 @@
 
 // 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);
+  uint32_t value = static_cast<uint32_t>(e_gamma * kSrgbInvOETFNumEntries);
   //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kSRGBInvOETFNumEntries - 1);
-  return kSRGBInvOETF[value];
+  value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
+  return kSrgbInvOETF[value];
 }
 
 Color srgbInvOetfLUT(Color e_gamma) {
@@ -461,21 +442,24 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 // Recovery map calculations
-
-uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) {
+uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) {
   float gain = 1.0f;
   if (y_sdr > 0.0f) {
     gain = y_hdr / y_sdr;
   }
 
-  if (gain < (1.0f / hdr_ratio)) gain = 1.0f / hdr_ratio;
-  if (gain > hdr_ratio) gain = hdr_ratio;
+  if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
+  if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
 
-  return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f  + 127.5f);
+  return static_cast<uint8_t>((log2(gain) - log2(metadata->minContentBoost))
+                            / (log2(metadata->maxContentBoost) - log2(metadata->minContentBoost))
+                            * 255.0f);
 }
 
-Color applyRecovery(Color e, float recovery, float hdr_ratio) {
-  float recoveryFactor = pow(hdr_ratio, recovery);
+Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata) {
+  float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery)
+                 + log2(metadata->maxContentBoost) * recovery;
+  float recoveryFactor = exp2(logBoost);
   return e * recoveryFactor;
 }
 
@@ -550,7 +534,7 @@
 }
 
 static float mapUintToFloat(uint8_t map_uint) {
-  return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
+  return static_cast<float>(map_uint) / 255.0f;
 }
 
 static float pythDistance(float x_diff, float y_diff) {
@@ -558,9 +542,9 @@
 }
 
 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
-  float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
-  float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
+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;
@@ -647,4 +631,4 @@
        | (0x3 << 30);  // Set alpha to 1.0
 }
 
-} // namespace android::recoverymap
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
deleted file mode 100644
index d5ad9a5..0000000
--- a/libs/jpegrecoverymap/recoverymaputils.cpp
+++ /dev/null
@@ -1,528 +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 <jpegrecoverymap/recoverymaputils.h>
-#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::recoverymap {
-
-/*
- * 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".
- */
-string Name(const string &prefix, const string &suffix) {
-  std::stringstream ss;
-  ss << prefix << ":" << suffix;
-  return ss.str();
-}
-
-/*
- * 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;
-}
-
-status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
-  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() {
-        gContainerItemState = NotStrarted;
-    }
-
-    enum ParseState {
-        NotStrarted,
-        Started,
-        Done
-    };
-
-    virtual DataMatchResult StartElement(const XmlTokenContext& context) {
-        string val;
-        if (context.BuildTokenValue(&val)) {
-            if (!val.compare(gContainerItemName)) {
-                gContainerItemState = Started;
-            } else {
-                if (gContainerItemState != Done) {
-                    gContainerItemState = NotStrarted;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
-        if (gContainerItemState == Started) {
-            gContainerItemState = Done;
-            lastAttributeName = "";
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
-        string val;
-        if (gContainerItemState == Started) {
-            if (context.BuildTokenValue(&val)) {
-                if (!val.compare(rangeScalingFactorAttrName)) {
-                    lastAttributeName = rangeScalingFactorAttrName;
-                } else if (!val.compare(transferFunctionAttrName)) {
-                    lastAttributeName = transferFunctionAttrName;
-                } else {
-                    lastAttributeName = "";
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
-        string val;
-        if (gContainerItemState == Started) {
-            if (context.BuildTokenValue(&val, true)) {
-                if (!lastAttributeName.compare(rangeScalingFactorAttrName)) {
-                    rangeScalingFactorStr = val;
-                } else if (!lastAttributeName.compare(transferFunctionAttrName)) {
-                    transferFunctionStr = val;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    bool getRangeScalingFactor(float* scaling_factor) {
-        if (gContainerItemState == Done) {
-            stringstream ss(rangeScalingFactorStr);
-            float val;
-            if (ss >> val) {
-                *scaling_factor = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    bool getTransferFunction(jpegr_transfer_function* transfer_function) {
-        if (gContainerItemState == Done) {
-            stringstream ss(transferFunctionStr);
-            int val;
-            if (ss >> val) {
-                *transfer_function = static_cast<jpegr_transfer_function>(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-        return true;
-    }
-
-private:
-    static const string gContainerItemName;
-    static const string rangeScalingFactorAttrName;
-    static const string transferFunctionAttrName;
-    string              rangeScalingFactorStr;
-    string              transferFunctionStr;
-    string              lastAttributeName;
-    ParseState          gContainerItemState;
-};
-
-// GContainer XMP constants - URI and namespace prefix
-const string kContainerUri        = "http://ns.google.com/photos/1.0/container/";
-const string kContainerPrefix     = "GContainer";
-
-// GContainer XMP constants - element and attribute names
-const string kConDirectory            = Name(kContainerPrefix, "Directory");
-const string kConItem                 = Name(kContainerPrefix, "Item");
-const string kConItemLength           = Name(kContainerPrefix, "ItemLength");
-const string kConItemMime             = Name(kContainerPrefix, "ItemMime");
-const string kConItemSemantic         = Name(kContainerPrefix, "ItemSemantic");
-const string kConVersion              = Name(kContainerPrefix, "Version");
-
-// GContainer XMP constants - element and attribute values
-const string kSemanticPrimary     = "Primary";
-const string kSemanticRecoveryMap = "RecoveryMap";
-const string kMimeImageJpeg       = "image/jpeg";
-
-const int kGContainerVersion      = 1;
-
-// GContainer XMP constants - names for XMP handlers
-const string XMPXmlHandler::gContainerItemName = kConItem;
-
-// RecoveryMap XMP constants - URI and namespace prefix
-const string kRecoveryMapUri      = "http://ns.google.com/photos/1.0/recoverymap/";
-const string kRecoveryMapPrefix   = "RecoveryMap";
-
-// RecoveryMap XMP constants - element and attribute names
-const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor");
-const string kMapTransferFunction   = Name(kRecoveryMapPrefix, "TransferFunction");
-const string kMapVersion            = Name(kRecoveryMapPrefix, "Version");
-
-const string kMapHdr10Metadata      = Name(kRecoveryMapPrefix, "HDR10Metadata");
-const string kMapHdr10MaxFall       = Name(kRecoveryMapPrefix, "HDR10MaxFALL");
-const string kMapHdr10MaxCll        = Name(kRecoveryMapPrefix, "HDR10MaxCLL");
-
-const string kMapSt2086Metadata     = Name(kRecoveryMapPrefix, "ST2086Metadata");
-const string kMapSt2086MaxLum       = Name(kRecoveryMapPrefix, "ST2086MaxLuminance");
-const string kMapSt2086MinLum       = Name(kRecoveryMapPrefix, "ST2086MinLuminance");
-const string kMapSt2086Primary      = Name(kRecoveryMapPrefix, "ST2086Primary");
-const string kMapSt2086Coordinate   = Name(kRecoveryMapPrefix, "ST2086Coordinate");
-const string kMapSt2086CoordinateX  = Name(kRecoveryMapPrefix, "ST2086CoordinateX");
-const string kMapSt2086CoordinateY  = Name(kRecoveryMapPrefix, "ST2086CoordinateY");
-
-// RecoveryMap XMP constants - element and attribute values
-const int kSt2086PrimaryRed       = 0;
-const int kSt2086PrimaryGreen     = 1;
-const int kSt2086PrimaryBlue      = 2;
-const int kSt2086PrimaryWhite     = 3;
-
-// RecoveryMap XMP constants - names for XMP handlers
-const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
-const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
-
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
-    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-
-    if (xmp_size < nameSpace.size()+2) {
-        // Data too short
-        return false;
-    }
-
-    if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
-        // Not correct namespace
-        return false;
-    }
-
-    // Position the pointers to the start of XMP XML portion
-    xmp_data += nameSpace.size()+1;
-    xmp_size -= nameSpace.size()+1;
-    XMPXmlHandler handler;
-
-    // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
-    while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
-        xmp_size--;
-    }
-
-    string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
-    MessageHandler msg_handler;
-    unique_ptr<XmlRule> rule(new XmlElementRule);
-    XmlReader reader(&handler, &msg_handler);
-    reader.StartParse(std::move(rule));
-    reader.Parse(str);
-    reader.FinishParse();
-    if (reader.HasErrors()) {
-        // Parse error
-        return false;
-    }
-
-    if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
-        return false;
-    }
-
-    if (!handler.getTransferFunction(&metadata->transferFunction)) {
-        return false;
-    }
-    return true;
-}
-
-string generateXmp(int secondary_image_length, jpegr_metadata& 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(kRecoveryMapPrefix, kRecoveryMapUri);
-  writer.WriteElementAndContent(kConVersion, kGContainerVersion);
-  writer.StartWritingElements(kConDirSeq);
-  size_t item_depth = writer.StartWritingElements(kLiItem);
-  writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
-  writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
-  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
-  writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor);
-  writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction);
-  if (metadata.transferFunction == JPEGR_TF_PQ) {
-    writer.StartWritingElement(kMapHdr10Metadata);
-    writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL);
-    writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL);
-    writer.StartWritingElement(kMapSt2086Metadata);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance);
-
-    // red
-    writer.StartWritingElement(kMapSt2086Coordinate);
-    writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
-    writer.FinishWritingElement();
-
-    // green
-    writer.StartWritingElement(kMapSt2086Coordinate);
-    writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
-    writer.FinishWritingElement();
-
-    // blue
-    writer.StartWritingElement(kMapSt2086Coordinate);
-    writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
-    writer.FinishWritingElement();
-
-    // white
-    writer.StartWritingElement(kMapSt2086Coordinate);
-    writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
-    writer.WriteAttributeNameAndValue(
-        kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
-    writer.FinishWritingElement();
-  }
-  writer.FinishWritingElementsToDepth(item_depth);
-  writer.StartWritingElements(kLiItem);
-  writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
-  writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
-  writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
-  writer.FinishWriting();
-
-  return ss.str();
-}
-
-/*
- * Helper function
- * Add J R entry to existing exif, or create a new one with J R entry if it's null.
- */
-status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
-  if (exif == nullptr || exif->data == nullptr) {
-    uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
-        0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
-        0x49, 0x49, 0x2A, 0x00,
-        0x08, 0x00, 0x00, 0x00,
-        0x01, 0x00,
-        0x4A, 0x52,
-        0x07, 0x00,
-        0x01, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00};
-    int pos = 0;
-    Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
-    return NO_ERROR;
-  }
-
-  int num_entry = 0;
-  uint8_t num_entry_low = 0;
-  uint8_t num_entry_high = 0;
-  bool use_big_endian = false;
-  if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
-      num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
-      num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
-  } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
-      use_big_endian = true;
-      num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
-      num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
-  } else {
-      return ERROR_JPEGR_METADATA_ERROR;
-  }
-  num_entry = (num_entry_high << 8) | num_entry_low;
-  num_entry += 1;
-  num_entry_low = num_entry & 0xff;
-  num_entry_high = (num_entry >> 8) & 0xff;
-
-  int pos = 0;
-  Write(dest, (uint8_t*)exif->data, 14, pos);
-
-  if (use_big_endian) {
-    Write(dest, &num_entry_high, 1, pos);
-    Write(dest, &num_entry_low, 1, pos);
-    uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
-          0x4A, 0x52,
-          0x00, 0x07,
-          0x00, 0x00, 0x00, 0x01,
-          0x00, 0x00, 0x00, 0x00};
-    Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
-  } else {
-    Write(dest, &num_entry_low, 1, pos);
-    Write(dest, &num_entry_high, 1, pos);
-    uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
-          0x4A, 0x52,
-          0x07, 0x00,
-          0x01, 0x00, 0x00, 0x00,
-          0x00, 0x00, 0x00, 0x00};
-    Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
-  }
-
-  Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
-
-  updateExifOffsets(dest,
-                    28, // start from the second tag, skip the "JR" tag
-                    num_entry - 1,
-                    use_big_endian);
-
-  return NO_ERROR;
-}
-
-/*
- * Helper function
- * Modify offsets in EXIF in place.
- */
-void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian) {
-  int num_entry = readValue(reinterpret_cast<uint8_t*>(exif->data), pos, 2, use_big_endian);
-  updateExifOffsets(exif, pos + 2, num_entry, use_big_endian);
-}
-
-void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian) {
-  for (int i = 0; i < num_entry; pos += EXIF_J_R_ENTRY_LENGTH, i++) {
-    int tag = readValue(reinterpret_cast<uint8_t*>(exif->data), pos, 2, use_big_endian);
-    bool need_to_update_offset = false;
-    if (tag == 0x8769) {
-      need_to_update_offset = true;
-      int sub_ifd_offset =
-              readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 8, 4, use_big_endian)
-              + 6  // "Exif\0\0";
-              + EXIF_J_R_ENTRY_LENGTH;
-      updateExifOffsets(exif, sub_ifd_offset, use_big_endian);
-    } else {
-      int data_format =
-              readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 2, 2, use_big_endian);
-      int num_of_components =
-              readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 4, 4, use_big_endian);
-      int data_length = findFormatLengthInBytes(data_format) * num_of_components;
-      if (data_length > 4) {
-        need_to_update_offset = true;
-      }
-    }
-
-    if (!need_to_update_offset) {
-      continue;
-    }
-
-    int offset = readValue(reinterpret_cast<uint8_t*>(exif->data), pos + 8, 4, use_big_endian);
-
-    offset += EXIF_J_R_ENTRY_LENGTH;
-
-    if (use_big_endian) {
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 11] = offset & 0xff;
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 10] = (offset >> 8) & 0xff;
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 9] = (offset >> 16) & 0xff;
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 8] = (offset >> 24) & 0xff;
-    } else {
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 8] = offset & 0xff;
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 9] = (offset >> 8) & 0xff;
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 10] = (offset >> 16) & 0xff;
-      reinterpret_cast<uint8_t*>(exif->data)[pos + 11] = (offset >> 24) & 0xff;
-    }
-  }
-}
-
-/*
- * Read data from the target position and target length in bytes;
- */
-int readValue(uint8_t* data, int pos, int length, bool use_big_endian) {
-  if (length == 2) {
-    if (use_big_endian) {
-      return (data[pos] << 8) | data[pos + 1];
-    } else {
-      return (data[pos + 1] << 8) | data[pos];
-    }
-  } else if (length == 4) {
-    if (use_big_endian) {
-      return (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
-    } else {
-      return (data[pos + 3] << 24) | (data[pos + 2] << 16) | (data[pos + 1] << 8) | data[pos];
-    }
-  } else {
-    // Not support for now.
-    ALOGE("Error in readValue(): pos=%d, length=%d", pos, length);
-    return -1;
-  }
-}
-
-/*
- * Helper function
- * Returns the length of data format in bytes
- */
-int findFormatLengthInBytes(int data_format) {
-  switch (data_format) {
-    case 1:  // unsigned byte
-    case 2:  // ascii strings
-    case 6:  // signed byte
-    case 7:  // undefined
-      return 1;
-
-    case 3:  // unsigned short
-    case 8:  // signed short
-      return 2;
-
-    case 4:  // unsigned long
-    case 9:  // signed long
-    case 11:  // single float
-      return 4;
-
-    case 5:  // unsigned rational
-    case 10:  // signed rational
-    case 12:  // double float
-      return 8;
-
-    default:
-      // should not hit here
-      ALOGE("Error in findFormatLengthInBytes(): data_format=%d", data_format);
-      return -1;
-  }
-}
-
-} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 39445f8..5a4edb2 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -25,12 +25,12 @@
     name: "libjpegrecoverymap_test",
     test_suites: ["device-tests"],
     srcs: [
-        "recoverymap_test.cpp",
+        "jpegr_test.cpp",
         "recoverymapmath_test.cpp",
     ],
     shared_libs: [
-        "libjpeg",
         "libimage_io",
+        "libjpeg",
         "liblog",
     ],
     static_libs: [
@@ -39,37 +39,38 @@
         "libjpegdecoder",
         "libjpegencoder",
         "libjpegrecoverymap",
+        "libskia",
     ],
 }
 
 cc_test {
-    name: "libjpegencoder_test",
+    name: "libjpegencoderhelper_test",
     test_suites: ["device-tests"],
     srcs: [
-        "jpegencoder_test.cpp",
+        "jpegencoderhelper_test.cpp",
     ],
     shared_libs: [
         "libjpeg",
         "liblog",
     ],
     static_libs: [
+        "libgtest",
         "libjpegencoder",
-        "libgtest",
     ],
 }
 
 cc_test {
-    name: "libjpegdecoder_test",
+    name: "libjpegdecoderhelper_test",
     test_suites: ["device-tests"],
     srcs: [
-        "jpegdecoder_test.cpp",
+        "jpegdecoderhelper_test.cpp",
     ],
     shared_libs: [
         "libjpeg",
         "liblog",
     ],
     static_libs: [
-        "libjpegdecoder",
         "libgtest",
+        "libjpegdecoder",
     ],
 }
diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
similarity index 77%
rename from libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
rename to libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
index 8e01351..2f32a56 100644
--- a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp
@@ -14,27 +14,27 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/jpegdecoderhelper.h>
 #include <gtest/gtest.h>
 #include <utils/Log.h>
 
 #include <fcntl.h>
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 #define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
 #define YUV_IMAGE_SIZE 20193
 #define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
 #define GREY_IMAGE_SIZE 20193
 
-class JpegDecoderTest : public testing::Test {
+class JpegDecoderHelperTest : public testing::Test {
 public:
     struct Image {
         std::unique_ptr<uint8_t[]> buffer;
         size_t size;
     };
-    JpegDecoderTest();
-    ~JpegDecoderTest();
+    JpegDecoderHelperTest();
+    ~JpegDecoderHelperTest();
 protected:
     virtual void SetUp();
     virtual void TearDown();
@@ -42,9 +42,9 @@
     Image mYuvImage, mGreyImage;
 };
 
-JpegDecoderTest::JpegDecoderTest() {}
+JpegDecoderHelperTest::JpegDecoderHelperTest() {}
 
-JpegDecoderTest::~JpegDecoderTest() {}
+JpegDecoderHelperTest::~JpegDecoderHelperTest() {}
 
 static size_t getFileSize(int fd) {
     struct stat st;
@@ -55,7 +55,7 @@
     return st.st_size; // bytes
 }
 
-static bool loadFile(const char filename[], JpegDecoderTest::Image* result) {
+static bool loadFile(const char filename[], JpegDecoderHelperTest::Image* result) {
     int fd = open(filename, O_CLOEXEC);
     if (fd < 0) {
         return false;
@@ -74,7 +74,7 @@
     return true;
 }
 
-void JpegDecoderTest::SetUp() {
+void JpegDecoderHelperTest::SetUp() {
     if (!loadFile(YUV_IMAGE, &mYuvImage)) {
         FAIL() << "Load file " << YUV_IMAGE << " failed";
     }
@@ -85,18 +85,18 @@
     mGreyImage.size = GREY_IMAGE_SIZE;
 }
 
-void JpegDecoderTest::TearDown() {}
+void JpegDecoderHelperTest::TearDown() {}
 
-TEST_F(JpegDecoderTest, decodeYuvImage) {
-    JpegDecoder decoder;
+TEST_F(JpegDecoderHelperTest, decodeYuvImage) {
+    JpegDecoderHelper decoder;
     EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
     ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
 }
 
-TEST_F(JpegDecoderTest, decodeGreyImage) {
-    JpegDecoder decoder;
+TEST_F(JpegDecoderHelperTest, decodeGreyImage) {
+    JpegDecoderHelper decoder;
     EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
     ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
 }
 
-}
\ No newline at end of file
+}  // namespace android::jpegrecoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
similarity index 82%
rename from libs/jpegrecoverymap/tests/jpegencoder_test.cpp
rename to libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
index 4cd2a5e..095ac2f 100644
--- a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/jpegencoder.h>
+#include <jpegrecoverymap/jpegencoderhelper.h>
 #include <gtest/gtest.h>
 #include <utils/Log.h>
 
 #include <fcntl.h>
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 #define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
 #define VALID_IMAGE_WIDTH 320
@@ -33,15 +33,15 @@
 #define INVALID_SIZE_IMAGE_HEIGHT 240
 #define JPEG_QUALITY 90
 
-class JpegEncoderTest : public testing::Test {
+class JpegEncoderHelperTest : public testing::Test {
 public:
     struct Image {
         std::unique_ptr<uint8_t[]> buffer;
         size_t width;
         size_t height;
     };
-    JpegEncoderTest();
-    ~JpegEncoderTest();
+    JpegEncoderHelperTest();
+    ~JpegEncoderHelperTest();
 protected:
     virtual void SetUp();
     virtual void TearDown();
@@ -49,9 +49,9 @@
     Image mValidImage, mInvalidSizeImage, mSingleChannelImage;
 };
 
-JpegEncoderTest::JpegEncoderTest() {}
+JpegEncoderHelperTest::JpegEncoderHelperTest() {}
 
-JpegEncoderTest::~JpegEncoderTest() {}
+JpegEncoderHelperTest::~JpegEncoderHelperTest() {}
 
 static size_t getFileSize(int fd) {
     struct stat st;
@@ -62,7 +62,7 @@
     return st.st_size; // bytes
 }
 
-static bool loadFile(const char filename[], JpegEncoderTest::Image* result) {
+static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) {
     int fd = open(filename, O_CLOEXEC);
     if (fd < 0) {
         return false;
@@ -81,7 +81,7 @@
     return true;
 }
 
-void JpegEncoderTest::SetUp() {
+void JpegEncoderHelperTest::SetUp() {
     if (!loadFile(VALID_IMAGE, &mValidImage)) {
         FAIL() << "Load file " << VALID_IMAGE << " failed";
     }
@@ -99,27 +99,27 @@
     mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
 }
 
-void JpegEncoderTest::TearDown() {}
+void JpegEncoderHelperTest::TearDown() {}
 
-TEST_F(JpegEncoderTest, validImage) {
-    JpegEncoder encoder;
+TEST_F(JpegEncoderHelperTest, validImage) {
+    JpegEncoderHelper encoder;
     EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width,
                                          mValidImage.height, JPEG_QUALITY, NULL, 0));
     ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
 }
 
-TEST_F(JpegEncoderTest, invalidSizeImage) {
-    JpegEncoder encoder;
+TEST_F(JpegEncoderHelperTest, invalidSizeImage) {
+    JpegEncoderHelper encoder;
     EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width,
                                           mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0));
 }
 
-TEST_F(JpegEncoderTest, singleChannelImage) {
-    JpegEncoder encoder;
+TEST_F(JpegEncoderHelperTest, singleChannelImage) {
+    JpegEncoderHelper encoder;
     EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
                                          mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
     ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
 }
 
-}
+}  // namespace android::jpegrecoverymap
 
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp
similarity index 66%
rename from libs/jpegrecoverymap/tests/recoverymap_test.cpp
rename to libs/jpegrecoverymap/tests/jpegr_test.cpp
index dfab76a..c0347e3 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/jpegr.h>
+#include <jpegrecoverymap/jpegrutils.h>
 #include <jpegrecoverymap/recoverymapmath.h>
-#include <jpegrecoverymap/recoverymaputils.h>
 #include <fcntl.h>
 #include <fstream>
 #include <gtest/gtest.h>
+#include <sys/time.h>
 #include <utils/Log.h>
 
 #define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
@@ -33,29 +34,26 @@
 #define SAVE_DECODING_RESULT true
 #define SAVE_INPUT_RGBA true
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
-class RecoveryMapTest : public testing::Test {
-public:
-  RecoveryMapTest();
-  ~RecoveryMapTest();
-protected:
-  virtual void SetUp();
-  virtual void TearDown();
-
-  struct jpegr_uncompressed_struct mRawP010Image;
-  struct jpegr_uncompressed_struct mRawYuv420Image;
-  struct jpegr_compressed_struct mJpegImage;
+struct Timer {
+  struct timeval StartingTime;
+  struct timeval EndingTime;
+  struct timeval ElapsedMicroseconds;
 };
 
-RecoveryMapTest::RecoveryMapTest() {}
-RecoveryMapTest::~RecoveryMapTest() {}
+void timerStart(Timer *t) {
+  gettimeofday(&t->StartingTime, nullptr);
+}
 
-void RecoveryMapTest::SetUp() {}
-void RecoveryMapTest::TearDown() {
-  free(mRawP010Image.data);
-  free(mRawYuv420Image.data);
-  free(mJpegImage.data);
+void timerStop(Timer *t) {
+  gettimeofday(&t->EndingTime, nullptr);
+}
+
+int64_t elapsedTime(Timer *t) {
+  t->ElapsedMicroseconds.tv_sec = t->EndingTime.tv_sec - t->StartingTime.tv_sec;
+  t->ElapsedMicroseconds.tv_usec = t->EndingTime.tv_usec - t->StartingTime.tv_usec;
+  return t->ElapsedMicroseconds.tv_sec * 1000000 + t->ElapsedMicroseconds.tv_usec;
 }
 
 static size_t getFileSize(int fd) {
@@ -89,22 +87,95 @@
   return true;
 }
 
-TEST_F(RecoveryMapTest, build) {
-  // Force all of the recovery map lib to be linked by calling all public functions.
-  RecoveryMap recovery_map;
-  recovery_map.encodeJPEGR(nullptr, static_cast<jpegr_transfer_function>(0), nullptr, 0, nullptr);
-  recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
-                           nullptr, 0, nullptr);
-  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
-                           nullptr);
-  recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
-  recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
+class JpegRTest : public testing::Test {
+public:
+  JpegRTest();
+  ~JpegRTest();
+
+protected:
+  virtual void SetUp();
+  virtual void TearDown();
+
+  struct jpegr_uncompressed_struct mRawP010Image;
+  struct jpegr_uncompressed_struct mRawYuv420Image;
+  struct jpegr_compressed_struct mJpegImage;
+};
+
+JpegRTest::JpegRTest() {}
+JpegRTest::~JpegRTest() {}
+
+void JpegRTest::SetUp() {}
+void JpegRTest::TearDown() {
+  free(mRawP010Image.data);
+  free(mRawYuv420Image.data);
+  free(mJpegImage.data);
 }
 
-TEST_F(RecoveryMapTest, writeXmpThenRead) {
+class JpegRBenchmark : public JpegR {
+public:
+ void BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
+                                   jr_metadata_ptr metadata, jr_uncompressed_ptr map);
+ void BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+                                jr_metadata_ptr metadata, jr_uncompressed_ptr dest);
+private:
+ const int kProfileCount = 10;
+};
+
+void JpegRBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image,
+                                                        jr_uncompressed_ptr p010Image,
+                                                        jr_metadata_ptr metadata,
+                                                        jr_uncompressed_ptr map) {
+  ASSERT_EQ(yuv420Image->width, p010Image->width);
+  ASSERT_EQ(yuv420Image->height, p010Image->height);
+
+  Timer genRecMapTime;
+
+  timerStart(&genRecMapTime);
+  for (auto i = 0; i < kProfileCount; i++) {
+      ASSERT_EQ(OK, generateRecoveryMap(
+          yuv420Image, p010Image, jpegr_transfer_function::JPEGR_TF_HLG, metadata, map));
+      if (i != kProfileCount - 1) delete[] static_cast<uint8_t *>(map->data);
+  }
+  timerStop(&genRecMapTime);
+
+  ALOGE("Generate Recovery Map:- Res = %i x %i, time = %f ms",
+        yuv420Image->width, yuv420Image->height,
+        elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f));
+
+}
+
+void JpegRBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image,
+                                                     jr_uncompressed_ptr map,
+                                                     jr_metadata_ptr metadata,
+                                                     jr_uncompressed_ptr dest) {
+  Timer applyRecMapTime;
+
+  timerStart(&applyRecMapTime);
+  for (auto i = 0; i < kProfileCount; i++) {
+      ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, dest));
+  }
+  timerStop(&applyRecMapTime);
+
+  ALOGE("Apply Recovery Map:- Res = %i x %i, time = %f ms",
+        yuv420Image->width, yuv420Image->height,
+        elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f));
+}
+
+TEST_F(JpegRTest, build) {
+  // Force all of the recovery map lib to be linked by calling all public functions.
+  JpegR jpegRCodec;
+  jpegRCodec.encodeJPEGR(nullptr, static_cast<jpegr_transfer_function>(0), nullptr, 0, nullptr);
+  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+                         nullptr, 0, nullptr);
+  jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+                         nullptr);
+  jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
+  jpegRCodec.decodeJPEGR(nullptr, nullptr, nullptr, false);
+}
+
+TEST_F(JpegRTest, writeXmpThenRead) {
   jpegr_metadata metadata_expected;
-  metadata_expected.transferFunction = JPEGR_TF_HLG;
-  metadata_expected.rangeScalingFactor = 1.25;
+  metadata_expected.maxContentBoost = 1.25;
   int length_expected = 1000;
   const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
   const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
@@ -120,12 +191,11 @@
 
   jpegr_metadata metadata_read;
   EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
-  ASSERT_EQ(metadata_expected.transferFunction, metadata_read.transferFunction);
-  ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor);
+  ASSERT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
 }
 
 /* Test Encode API-0 and decode */
-TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
+TEST_F(JpegRTest, encodeFromP010ThenDecode) {
   int ret;
 
   // Load input files.
@@ -136,12 +206,12 @@
   mRawP010Image.height = TEST_IMAGE_HEIGHT;
   mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
 
-  RecoveryMap recoveryMap;
+  JpegR jpegRCodec;
 
   jpegr_compressed_struct jpegR;
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
-  ret = recoveryMap.encodeJPEGR(
+  ret = jpegRCodec.encodeJPEGR(
       &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
@@ -159,7 +229,7 @@
   jpegr_uncompressed_struct decodedJpegR;
   int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
   decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -178,7 +248,7 @@
 }
 
 /* Test Encode API-1 and decode */
-TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) {
+TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) {
   int ret;
 
   // Load input files.
@@ -196,12 +266,12 @@
   mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
   mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
 
-  RecoveryMap recoveryMap;
+  JpegR jpegRCodec;
 
   jpegr_compressed_struct jpegR;
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
-  ret = recoveryMap.encodeJPEGR(
+  ret = jpegRCodec.encodeJPEGR(
       &mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR,
       DEFAULT_JPEG_QUALITY, nullptr);
   if (ret != OK) {
@@ -220,7 +290,7 @@
   jpegr_uncompressed_struct decodedJpegR;
   int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
   decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -239,7 +309,7 @@
 }
 
 /* Test Encode API-2 and decode */
-TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
+TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
   int ret;
 
   // Load input files.
@@ -262,12 +332,12 @@
   }
   mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
 
-  RecoveryMap recoveryMap;
+  JpegR jpegRCodec;
 
   jpegr_compressed_struct jpegR;
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
-  ret = recoveryMap.encodeJPEGR(
+  ret = jpegRCodec.encodeJPEGR(
       &mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
@@ -285,7 +355,7 @@
   jpegr_uncompressed_struct decodedJpegR;
   int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
   decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -304,7 +374,7 @@
 }
 
 /* Test Encode API-3 and decode */
-TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) {
+TEST_F(JpegRTest, encodeFromJpegThenDecode) {
   int ret;
 
   // Load input files.
@@ -343,12 +413,12 @@
   }
   mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
 
-  RecoveryMap recoveryMap;
+  JpegR jpegRCodec;
 
   jpegr_compressed_struct jpegR;
   jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
-  ret = recoveryMap.encodeJPEGR(
+  ret = jpegRCodec.encodeJPEGR(
       &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
@@ -366,7 +436,7 @@
   jpegr_uncompressed_struct decodedJpegR;
   int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
   decodedJpegR.data = malloc(decodedJpegRSize);
-  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -384,4 +454,46 @@
   free(decodedJpegR.data);
 }
 
-} // namespace android::recoverymap
+TEST_F(JpegRTest, ProfileRecoveryMapFuncs) {
+  const size_t kWidth = TEST_IMAGE_WIDTH;
+  const size_t kHeight = TEST_IMAGE_HEIGHT;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawP010Image.width = kWidth;
+  mRawP010Image.height = kHeight;
+  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+  if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawYuv420Image.width = kWidth;
+  mRawYuv420Image.height = kHeight;
+  mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+  JpegRBenchmark benchmark;
+
+  jpegr_metadata metadata = { .version = 1,
+                              .maxContentBoost = 8.0f,
+                              .minContentBoost = 1.0f / 8.0f };
+
+  jpegr_uncompressed_struct map = { .data = NULL,
+                                    .width = 0,
+                                    .height = 0,
+                                    .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
+
+  benchmark.BenchmarkGenerateRecoveryMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
+
+  const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4;
+  auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
+  jpegr_uncompressed_struct dest = { .data = bufferDst.get(),
+                                     .width = 0,
+                                     .height = 0,
+                                     .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED };
+
+  benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest);
+}
+
+} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index 1d522d1..6c61ff1 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -19,7 +19,7 @@
 #include <gmock/gmock.h>
 #include <jpegrecoverymap/recoverymapmath.h>
 
-namespace android::recoverymap {
+namespace android::jpegrecoverymap {
 
 class RecoveryMapMathTest : public testing::Test {
 public:
@@ -42,7 +42,7 @@
   }
 
   float Map(uint8_t e) {
-    return (static_cast<float>(e) - 127.5f) / 127.5f;
+    return static_cast<float>(e) / 255.0f;
   }
 
   Color ColorMin(Color e1, Color e2) {
@@ -88,10 +88,10 @@
     return luminance_scaled * scale_factor;
   }
 
-  Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) {
+  Color Recover(Color yuv_gamma, float recovery, jr_metadata_ptr metadata) {
     Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
     Color rgb = srgbInvOetf(rgb_gamma);
-    return applyRecovery(rgb, recovery, range_scaling_factor);
+    return applyRecovery(rgb, recovery, metadata);
   }
 
   jpegr_uncompressed_struct Yuv420Image() {
@@ -518,59 +518,95 @@
 }
 
 TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
-    float increment = 1.0 / 1024.0;
-    float value = 0.0f;
-    for (int idx = 0; idx < 1024; idx++, value += increment) {
+    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(RecoveryMapMathTest, HlgInvOetfLUT) {
-    float increment = 1.0 / 1024.0;
-    float value = 0.0f;
-    for (int idx = 0; idx < 1024; idx++, value += increment) {
+    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(RecoveryMapMathTest, pqOetfLUT) {
-    float increment = 1.0 / 1024.0;
-    float value = 0.0f;
-    for (int idx = 0; idx < 1024; idx++, value += increment) {
+    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(RecoveryMapMathTest, hlgOetfLUT) {
-    float increment = 1.0 / 1024.0;
-    float value = 0.0f;
-    for (int idx = 0; idx < 1024; idx++, value += increment) {
+    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(RecoveryMapMathTest, srgbInvOetfLUT) {
-    float increment = 1.0 / 1024.0;
-    float value = 0.0f;
-    for (int idx = 0; idx < 1024; idx++, value += increment) {
+    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(RecoveryMapMathTest, applyRecoveryLUT) {
-  float increment = 2.0 / kRecoveryFactorNumEntries;
-  for (float hdrRatio = 1.0f; hdrRatio <= 10.0f; hdrRatio += 1.0f)  {
-    RecoveryLUT recoveryLUT(hdrRatio);
-    for (float value = -1.0f; value <= -1.0f; value += increment) {
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, hdrRatio),
+  for (int boost = 1; boost <= 10; boost++) {
+    jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
+                                .minContentBoost = 1.0f / static_cast<float>(boost) };
+    RecoveryLUT recoveryLUT(&metadata);
+    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
                       applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, hdrRatio),
+      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
                       applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, hdrRatio),
+      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
                       applyRecoveryLUT(RgbRed(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, hdrRatio),
+      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
                       applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
-      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, hdrRatio),
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
+                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+    }
+  }
+
+  for (int boost = 1; boost <= 10; boost++) {
+    jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
+                                .minContentBoost = 1.0f };
+    RecoveryLUT recoveryLUT(&metadata);
+    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
+                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
+                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
+                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
+                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
+                      applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
+    }
+  }
+
+  for (int boost = 1; boost <= 10; boost++) {
+    jpegr_metadata metadata = { .maxContentBoost = static_cast<float>(boost),
+                                .minContentBoost = 1.0f / pow(static_cast<float>(boost),
+                                                              1.0f / 3.0f) };
+    RecoveryLUT recoveryLUT(&metadata);
+    for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) {
+      float value = static_cast<float>(idx) / static_cast<float>(kRecoveryFactorNumEntries - 1);
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata),
+                      applyRecoveryLUT(RgbBlack(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata),
+                      applyRecoveryLUT(RgbWhite(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata),
+                      applyRecoveryLUT(RgbRed(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata),
+                      applyRecoveryLUT(RgbGreen(), value, recoveryLUT));
+      EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata),
                       applyRecoveryLUT(RgbBlue(), value, recoveryLUT));
     }
   }
@@ -623,60 +659,121 @@
 }
 
 TEST_F(RecoveryMapMathTest, EncodeRecovery) {
-  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, 4.0f), 127);
-  EXPECT_EQ(encodeRecovery(0.0f, 1.0f, 4.0f), 127);
-  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, 4.0f), 0);
-  EXPECT_EQ(encodeRecovery(0.5f, 0.0f, 4.0f), 0);
+  jpegr_metadata metadata = { .maxContentBoost = 4.0f,
+                              .minContentBoost = 1.0f / 4.0f };
 
-  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, 4.0f), 127);
-  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, 4.0f), 255);
-  EXPECT_EQ(encodeRecovery(1.0f, 5.0f, 4.0f), 255);
-  EXPECT_EQ(encodeRecovery(4.0f, 1.0f, 4.0f), 0);
-  EXPECT_EQ(encodeRecovery(4.0f, 0.5f, 4.0f), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 4.0f), 191);
-  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 4.0f), 63);
+  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 127);
+  EXPECT_EQ(encodeRecovery(0.0f, 1.0f, &metadata), 127);
+  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeRecovery(0.5f, 0.0f, &metadata), 0);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 2.0f), 255);
-  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 2.0f), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, 2.0f), 191);
-  EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, 2.0f), 63);
+  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 127);
+  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 255);
+  EXPECT_EQ(encodeRecovery(1.0f, 5.0f, &metadata), 255);
+  EXPECT_EQ(encodeRecovery(4.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeRecovery(4.0f, 0.5f, &metadata), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 191);
+  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 63);
 
-  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, 8.0f), 255);
-  EXPECT_EQ(encodeRecovery(8.0f, 1.0f, 8.0f), 0);
-  EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, 8.0f), 191);
-  EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, 8.0f), 63);
+  metadata.maxContentBoost = 2.0f;
+  metadata.minContentBoost = 1.0f / 2.0f;
+
+  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 255);
+  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, &metadata), 191);
+  EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, &metadata), 63);
+
+  metadata.maxContentBoost = 8.0f;
+  metadata.minContentBoost = 1.0f / 8.0f;
+
+  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeRecovery(8.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, &metadata), 191);
+  EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, &metadata), 63);
+
+  metadata.maxContentBoost = 8.0f;
+  metadata.minContentBoost = 1.0f;
+
+  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+
+  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 170);
+  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 85);
+
+  metadata.maxContentBoost = 8.0f;
+  metadata.minContentBoost = 0.5f;
+
+  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 63);
+  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0);
+
+  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 63);
+  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255);
+  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 191);
+  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 127);
+  EXPECT_EQ(encodeRecovery(1.0f, 0.7071f, &metadata), 31);
+  EXPECT_EQ(encodeRecovery(1.0f, 0.5f, &metadata), 0);
 }
 
 TEST_F(RecoveryMapMathTest, ApplyRecovery) {
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), -1.0f, 4.0f), RgbBlack());
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, 4.0f), RgbBlack());
-  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, 4.0f), RgbBlack());
+  jpegr_metadata metadata = { .maxContentBoost = 4.0f,
+                              .minContentBoost = 1.0f / 4.0f };
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 4.0f), RgbWhite() / 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 4.0f), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 4.0f), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 4.0f), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 4.0f), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack());
+  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, &metadata), RgbBlack());
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 2.0f), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 2.0f), RgbWhite() / 1.41421f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 2.0f), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 2.0f), RgbWhite() * 1.41421f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 2.0f), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
 
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 8.0f), RgbWhite() / 8.0f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 8.0f), RgbWhite() / 2.82843f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 8.0f), RgbWhite());
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 8.0f), RgbWhite() * 2.82843f);
-  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 8.0f), RgbWhite() * 8.0f);
+  metadata.maxContentBoost = 2.0f;
+  metadata.minContentBoost = 1.0f / 2.0f;
+
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
+
+  metadata.maxContentBoost = 8.0f;
+  metadata.minContentBoost = 1.0f / 8.0f;
+
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+
+  metadata.maxContentBoost = 8.0f;
+  metadata.minContentBoost = 1.0f;
+
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
+
+  metadata.maxContentBoost = 8.0f;
+  metadata.minContentBoost = 0.5f;
+
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(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(applyRecovery(e, -1.0f, 4.0f), e / 4.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, -0.5f, 4.0f), e / 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, 4.0f), e);
-  EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, 4.0f), e * 2.0f);
-  EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, 4.0f), e * 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, &metadata), e / 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(e, 0.25f, &metadata), e / 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, &metadata), e);
+  EXPECT_RGB_NEAR(applyRecovery(e, 0.75f, &metadata), e * 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, &metadata), e * 4.0f);
 }
 
 TEST_F(RecoveryMapMathTest, GetYuv420Pixel) {
@@ -785,8 +882,10 @@
       // 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, idwTable),
+      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));
     }
   }
 }
@@ -882,60 +981,89 @@
 }
 
 TEST_F(RecoveryMapMathTest, ApplyMap) {
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, 8.0f),
+  jpegr_metadata 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, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata),
                 RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata),
                   RgbRed() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata),
                   RgbGreen() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata),
                   RgbBlue() * 8.0f);
 
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata),
                 RgbWhite() * sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata),
                 RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata),
                   RgbRed() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata),
                   RgbGreen() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata),
                   RgbBlue() * sqrt(8.0f));
 
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
                 RgbWhite());
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata),
                 RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata),
                   RgbRed());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata),
                   RgbGreen());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata),
                   RgbBlue());
 
-  EXPECT_RGB_EQ(Recover(YuvWhite(), -0.5f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
                 RgbWhite() / sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), -0.5f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata),
                 RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -0.5f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata),
                   RgbRed() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -0.5f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata),
                   RgbGreen() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -0.5f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata),
                   RgbBlue() / sqrt(8.0f));
 
-  EXPECT_RGB_EQ(Recover(YuvWhite(), -1.0f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
                 RgbWhite() / 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvBlack(), -1.0f, 8.0f),
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata),
                 RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -1.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata),
                   RgbRed() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -1.0f, 8.0f),
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata),
                   RgbGreen() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -1.0f, 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::recoverymap
+} // namespace android::jpegrecoverymap
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 750338b..49e1cba 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -144,6 +144,7 @@
     srcs: [
         "EGL/BlobCache.cpp",
         "EGL/FileBlobCache.cpp",
+        "EGL/MultifileBlobCache.cpp",
     ],
     export_include_dirs: ["EGL"],
 }
@@ -160,7 +161,6 @@
     srcs: [
         "EGL/egl_tls.cpp",
         "EGL/egl_cache.cpp",
-        "EGL/egl_cache_multifile.cpp",
         "EGL/egl_display.cpp",
         "EGL/egl_object.cpp",
         "EGL/egl_layers.cpp",
@@ -205,6 +205,11 @@
     srcs: [
         "EGL/BlobCache.cpp",
         "EGL/BlobCache_test.cpp",
+        "EGL/MultifileBlobCache.cpp",
+        "EGL/MultifileBlobCache_test.cpp",
+    ],
+    shared_libs: [
+        "libutils",
     ],
 }
 
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
new file mode 100644
index 0000000..99af299
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -0,0 +1,689 @@
+/*
+ ** Copyright 2022, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+
+#include "MultifileBlobCache.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <algorithm>
+#include <chrono>
+#include <limits>
+#include <locale>
+
+#include <utils/JenkinsHash.h>
+
+using namespace std::literals;
+
+namespace {
+
+// Open the file and determine the size of the value it contains
+size_t getValueSizeFromFile(int fd, const std::string& entryPath) {
+    // Read the beginning of the file to get header
+    android::MultifileHeader header;
+    size_t result = read(fd, static_cast<void*>(&header), sizeof(android::MultifileHeader));
+    if (result != sizeof(android::MultifileHeader)) {
+        ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(),
+              std::strerror(errno));
+        return 0;
+    }
+
+    return header.valueSize;
+}
+
+// Helper function to close entries or free them
+void freeHotCacheEntry(android::MultifileHotCache& entry) {
+    if (entry.entryFd != -1) {
+        // If we have an fd, then this entry was added to hot cache via INIT or GET
+        // We need to unmap and close the entry
+        munmap(entry.entryBuffer, entry.entrySize);
+        close(entry.entryFd);
+    } else {
+        // Otherwise, this was added to hot cache during SET, so it was never mapped
+        // and fd was only on the deferred thread.
+        delete[] entry.entryBuffer;
+    }
+}
+
+} // namespace
+
+namespace android {
+
+MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize,
+                                       const std::string& baseDir)
+      : mInitialized(false),
+        mMaxTotalSize(maxTotalSize),
+        mTotalCacheSize(0),
+        mHotCacheLimit(maxHotCacheSize),
+        mHotCacheSize(0),
+        mWorkerThreadIdle(true) {
+    if (baseDir.empty()) {
+        ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
+        return;
+    }
+
+    // Establish the name of our multifile directory
+    mMultifileDirName = baseDir + ".multifile";
+
+    // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache
+    mMaxKeySize = mHotCacheLimit / 4;
+    mMaxValueSize = mHotCacheLimit / 2;
+
+    ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
+          mMaxKeySize, mMaxValueSize);
+
+    // Initialize our cache with the contents of the directory
+    mTotalCacheSize = 0;
+
+    // Create the worker thread
+    mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
+
+    // See if the dir exists, and initialize using its contents
+    struct stat st;
+    if (stat(mMultifileDirName.c_str(), &st) == 0) {
+        // Read all the files and gather details, then preload their contents
+        DIR* dir;
+        struct dirent* entry;
+        if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
+            while ((entry = readdir(dir)) != nullptr) {
+                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                    continue;
+                }
+
+                std::string entryName = entry->d_name;
+                std::string fullPath = mMultifileDirName + "/" + entryName;
+
+                // The filename is the same as the entryHash
+                uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
+
+                ALOGV("INIT: Checking entry %u", entryHash);
+
+                // Look up the details of the file
+                struct stat st;
+                if (stat(fullPath.c_str(), &st) != 0) {
+                    ALOGE("Failed to stat %s", fullPath.c_str());
+                    return;
+                }
+
+                // Open the file so we can read its header
+                int fd = open(fullPath.c_str(), O_RDONLY);
+                if (fd == -1) {
+                    ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
+                          std::strerror(errno));
+                    return;
+                }
+
+                // Look up the details we track about each file
+                size_t valueSize = getValueSizeFromFile(fd, fullPath);
+
+                // If the cache entry is damaged or no good, remove it
+                // TODO: Perform any other checks
+                if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) {
+                    ALOGV("INIT: Entry %u has a problem! Removing.", entryHash);
+                    if (remove(fullPath.c_str()) != 0) {
+                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                    }
+                    continue;
+                }
+
+                ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
+
+                // Note: Converting from off_t (signed) to size_t (unsigned)
+                size_t fileSize = static_cast<size_t>(st.st_size);
+                time_t accessTime = st.st_atime;
+
+                // Track details for rapid lookup later
+                trackEntry(entryHash, valueSize, fileSize, accessTime);
+
+                // Track the total size
+                increaseTotalCacheSize(fileSize);
+
+                // Preload the entry for fast retrieval
+                if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
+                    // Memory map the file
+                    uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
+                            mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+                    if (mappedEntry == MAP_FAILED) {
+                        ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+                    }
+
+                    ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
+                          "entryHash %u",
+                          fd, mappedEntry, entryHash);
+
+                    // Track the details of the preload so they can be retrieved later
+                    if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
+                        ALOGE("INIT Failed to add %u to hot cache", entryHash);
+                        munmap(mappedEntry, fileSize);
+                        close(fd);
+                        return;
+                    }
+                } else {
+                    close(fd);
+                }
+            }
+            closedir(dir);
+        } else {
+            ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
+        }
+    } else {
+        // If the multifile directory does not exist, create it and start from scratch
+        if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
+            ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
+        }
+    }
+
+    mInitialized = true;
+}
+
+MultifileBlobCache::~MultifileBlobCache() {
+    if (!mInitialized) {
+        return;
+    }
+
+    // Inform the worker thread we're done
+    ALOGV("DESCTRUCTOR: Shutting down worker thread");
+    DeferredTask task(TaskCommand::Exit);
+    queueTask(std::move(task));
+
+    // Wait for it to complete
+    ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
+    waitForWorkComplete();
+    if (mTaskThread.joinable()) {
+        mTaskThread.join();
+    }
+}
+
+// Set will add the entry to hot cache and start a deferred process to write it to disk
+void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
+                             EGLsizeiANDROID valueSize) {
+    if (!mInitialized) {
+        return;
+    }
+
+    // Ensure key and value are under their limits
+    if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
+        ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+              valueSize, mMaxValueSize);
+        return;
+    }
+
+    // Generate a hash of the key and use it to track this entry
+    uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+    size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
+
+    // If we're going to be over the cache limit, kick off a trim to clear space
+    if (getTotalSize() + fileSize > mMaxTotalSize) {
+        ALOGV("SET: Cache is full, calling trimCache to clear space");
+        trimCache(mMaxTotalSize);
+    }
+
+    ALOGV("SET: Add %u to cache", entryHash);
+
+    uint8_t* buffer = new uint8_t[fileSize];
+
+    // Write the key and value after the header
+    android::MultifileHeader header = {keySize, valueSize};
+    memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
+           sizeof(android::MultifileHeader));
+    memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
+           keySize);
+    memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
+           static_cast<const void*>(value), valueSize);
+
+    std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+    // Track the size and access time for quick recall
+    trackEntry(entryHash, valueSize, fileSize, time(0));
+
+    // Update the overall cache size
+    increaseTotalCacheSize(fileSize);
+
+    // Keep the entry in hot cache for quick retrieval
+    ALOGV("SET: Adding %u to hot cache.", entryHash);
+
+    // Sending -1 as the fd indicates we don't have an fd for this
+    if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
+        ALOGE("GET: Failed to add %u to hot cache", entryHash);
+        return;
+    }
+
+    // Track that we're creating a pending write for this entry
+    // Include the buffer to handle the case when multiple writes are pending for an entry
+    mDeferredWrites.insert(std::make_pair(entryHash, buffer));
+
+    // Create deferred task to write to storage
+    ALOGV("SET: Adding task to queue.");
+    DeferredTask task(TaskCommand::WriteToDisk);
+    task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
+    queueTask(std::move(task));
+}
+
+// Get will check the hot cache, then load it from disk if needed
+EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
+                                        EGLsizeiANDROID valueSize) {
+    if (!mInitialized) {
+        return 0;
+    }
+
+    // Ensure key and value are under their limits
+    if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
+        ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+              valueSize, mMaxValueSize);
+        return 0;
+    }
+
+    // Generate a hash of the key and use it to track this entry
+    uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+    // See if we have this file
+    if (!contains(entryHash)) {
+        ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
+        return 0;
+    }
+
+    // Look up the data for this entry
+    MultifileEntryStats entryStats = getEntryStats(entryHash);
+
+    size_t cachedValueSize = entryStats.valueSize;
+    if (cachedValueSize > valueSize) {
+        ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
+              "size (%zu)",
+              valueSize, entryHash, cachedValueSize);
+        return cachedValueSize;
+    }
+
+    // We have the file and have enough room to write it out, return the entry
+    ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
+
+    // Look up the size of the file
+    size_t fileSize = entryStats.fileSize;
+    if (keySize > fileSize) {
+        ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
+              "file",
+              keySize, fileSize);
+        return 0;
+    }
+
+    std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+    // Open the hashed filename path
+    uint8_t* cacheEntry = 0;
+
+    // Check hot cache
+    if (mHotCache.find(entryHash) != mHotCache.end()) {
+        ALOGV("GET: HotCache HIT for entry %u", entryHash);
+        cacheEntry = mHotCache[entryHash].entryBuffer;
+    } else {
+        ALOGV("GET: HotCache MISS for entry: %u", entryHash);
+
+        if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) {
+            // Wait for writes to complete if there is an outstanding write for this entry
+            ALOGV("GET: Waiting for write to complete for %u", entryHash);
+            waitForWorkComplete();
+        }
+
+        // Open the entry file
+        int fd = open(fullPath.c_str(), O_RDONLY);
+        if (fd == -1) {
+            ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
+                  std::strerror(errno));
+            return 0;
+        }
+
+        // Memory map the file
+        cacheEntry =
+                reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+        if (cacheEntry == MAP_FAILED) {
+            ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+            close(fd);
+            return 0;
+        }
+
+        ALOGV("GET: Adding %u to hot cache", entryHash);
+        if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
+            ALOGE("GET: Failed to add %u to hot cache", entryHash);
+            return 0;
+        }
+
+        cacheEntry = mHotCache[entryHash].entryBuffer;
+    }
+
+    // Ensure the header matches
+    MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
+    if (header->keySize != keySize || header->valueSize != valueSize) {
+        ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
+              "to cache header values for fullPath: %s",
+              keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
+        removeFromHotCache(entryHash);
+        return 0;
+    }
+
+    // Compare the incoming key with our stored version (the beginning of the entry)
+    uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
+    int compare = memcmp(cachedKey, key, keySize);
+    if (compare != 0) {
+        ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
+        removeFromHotCache(entryHash);
+        return 0;
+    }
+
+    // Remaining entry following the key is the value
+    uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
+    memcpy(value, cachedValue, cachedValueSize);
+
+    return cachedValueSize;
+}
+
+void MultifileBlobCache::finish() {
+    if (!mInitialized) {
+        return;
+    }
+
+    // Wait for all deferred writes to complete
+    ALOGV("FINISH: Waiting for work to complete.");
+    waitForWorkComplete();
+
+    // Close all entries in the hot cache
+    for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
+        uint32_t entryHash = hotCacheIter->first;
+        MultifileHotCache entry = hotCacheIter->second;
+
+        ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
+        freeHotCacheEntry(entry);
+
+        mHotCache.erase(hotCacheIter++);
+    }
+}
+
+void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
+                                    time_t accessTime) {
+    mEntries.insert(entryHash);
+    mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
+}
+
+bool MultifileBlobCache::contains(uint32_t hashEntry) const {
+    return mEntries.find(hashEntry) != mEntries.end();
+}
+
+MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
+    return mEntryStats[entryHash];
+}
+
+void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
+    mTotalCacheSize += fileSize;
+}
+
+void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
+    mTotalCacheSize -= fileSize;
+}
+
+bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
+                                       size_t newEntrySize) {
+    ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
+
+    // Clear space if we need to
+    if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
+        ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
+              "mHotCacheLimit "
+              "(%zu), freeing up space for %u",
+              mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
+
+        // Wait for all the files to complete writing so our hot cache is accurate
+        waitForWorkComplete();
+
+        // Free up old entries until under the limit
+        for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
+            uint32_t oldEntryHash = hotCacheIter->first;
+            MultifileHotCache oldEntry = hotCacheIter->second;
+
+            // Move our iterator before deleting the entry
+            hotCacheIter++;
+            if (!removeFromHotCache(oldEntryHash)) {
+                ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
+                return false;
+            }
+
+            // Clear at least half the hot cache
+            if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
+                ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
+                break;
+            }
+        }
+    }
+
+    // Track it
+    mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
+    mHotCacheSize += newEntrySize;
+
+    ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
+
+    return true;
+}
+
+bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
+    if (mHotCache.find(entryHash) != mHotCache.end()) {
+        ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
+
+        // Wait for all the files to complete writing so our hot cache is accurate
+        waitForWorkComplete();
+
+        ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
+        MultifileHotCache entry = mHotCache[entryHash];
+        freeHotCacheEntry(entry);
+
+        // Delete the entry from our tracking
+        mHotCacheSize -= entry.entrySize;
+        mHotCache.erase(entryHash);
+
+        return true;
+    }
+
+    return false;
+}
+
+bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
+    // Walk through our map of sorted last access times and remove files until under the limit
+    for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
+        uint32_t entryHash = cacheEntryIter->first;
+
+        ALOGV("LRU: Removing entryHash %u", entryHash);
+
+        // Track the overall size
+        MultifileEntryStats entryStats = getEntryStats(entryHash);
+        decreaseTotalCacheSize(entryStats.fileSize);
+
+        // Remove it from hot cache if present
+        removeFromHotCache(entryHash);
+
+        // Remove it from the system
+        std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
+        if (remove(entryPath.c_str()) != 0) {
+            ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
+            return false;
+        }
+
+        // Increment the iterator before clearing the entry
+        cacheEntryIter++;
+
+        // Delete the entry from our tracking
+        size_t count = mEntryStats.erase(entryHash);
+        if (count != 1) {
+            ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
+            return false;
+        }
+
+        // See if it has been reduced enough
+        size_t totalCacheSize = getTotalSize();
+        if (totalCacheSize <= cacheLimit) {
+            // Success
+            ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
+            return true;
+        }
+    }
+
+    ALOGV("LRU: Cache is emptry");
+    return false;
+}
+
+// When removing files, what fraction of the overall limit should be reached when removing files
+// A divisor of two will decrease the cache to 50%, four to 25% and so on
+constexpr uint32_t kCacheLimitDivisor = 2;
+
+// Calculate the cache size and remove old entries until under the limit
+void MultifileBlobCache::trimCache(size_t cacheByteLimit) {
+    // Start with the value provided by egl_cache
+    size_t limit = cacheByteLimit;
+
+    // Wait for all deferred writes to complete
+    waitForWorkComplete();
+
+    size_t size = getTotalSize();
+
+    // If size is larger than the threshold, remove files using LRU
+    if (size > limit) {
+        ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries",
+              cacheByteLimit);
+        if (!applyLRU(limit / kCacheLimitDivisor)) {
+            ALOGE("Error when clearing multifile shader cache");
+            return;
+        }
+    }
+}
+
+// This function performs a task.  It only knows how to write files to disk,
+// but it could be expanded if needed.
+void MultifileBlobCache::processTask(DeferredTask& task) {
+    switch (task.getTaskCommand()) {
+        case TaskCommand::Exit: {
+            ALOGV("DEFERRED: Shutting down");
+            return;
+        }
+        case TaskCommand::WriteToDisk: {
+            uint32_t entryHash = task.getEntryHash();
+            std::string& fullPath = task.getFullPath();
+            uint8_t* buffer = task.getBuffer();
+            size_t bufferSize = task.getBufferSize();
+
+            // Create the file or reset it if already present, read+write for user only
+            int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+            if (fd == -1) {
+                ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
+                      fullPath.c_str(), std::strerror(errno));
+                return;
+            }
+
+            ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
+
+            ssize_t result = write(fd, buffer, bufferSize);
+            if (result != bufferSize) {
+                ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
+                      std::strerror(errno));
+                return;
+            }
+
+            ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
+            close(fd);
+
+            // Erase the entry from mDeferredWrites
+            // Since there could be multiple outstanding writes for an entry, find the matching one
+            typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
+            std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
+            for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
+                if (it->second == buffer) {
+                    ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second);
+                    mDeferredWrites.erase(it);
+                    break;
+                }
+            }
+
+            return;
+        }
+        default: {
+            ALOGE("DEFERRED: Unhandled task type");
+            return;
+        }
+    }
+}
+
+// This function will wait until tasks arrive, then execute them
+// If the exit command is submitted, the loop will terminate
+void MultifileBlobCache::processTasksImpl(bool* exitThread) {
+    while (true) {
+        std::unique_lock<std::mutex> lock(mWorkerMutex);
+        if (mTasks.empty()) {
+            ALOGV("WORKER: No tasks available, waiting");
+            mWorkerThreadIdle = true;
+            mWorkerIdleCondition.notify_all();
+            // Only wake if notified and command queue is not empty
+            mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
+        }
+
+        ALOGV("WORKER: Task available, waking up.");
+        mWorkerThreadIdle = false;
+        DeferredTask task = std::move(mTasks.front());
+        mTasks.pop();
+
+        if (task.getTaskCommand() == TaskCommand::Exit) {
+            ALOGV("WORKER: Exiting work loop.");
+            *exitThread = true;
+            mWorkerThreadIdle = true;
+            mWorkerIdleCondition.notify_one();
+            return;
+        }
+
+        lock.unlock();
+        processTask(task);
+    }
+}
+
+// Process tasks until the exit task is submitted
+void MultifileBlobCache::processTasks() {
+    while (true) {
+        bool exitThread = false;
+        processTasksImpl(&exitThread);
+        if (exitThread) {
+            break;
+        }
+    }
+}
+
+// Add a task to the queue to be processed by the worker thread
+void MultifileBlobCache::queueTask(DeferredTask&& task) {
+    std::lock_guard<std::mutex> queueLock(mWorkerMutex);
+    mTasks.emplace(std::move(task));
+    mWorkAvailableCondition.notify_one();
+}
+
+// Wait until all tasks have been completed
+void MultifileBlobCache::waitForWorkComplete() {
+    std::unique_lock<std::mutex> lock(mWorkerMutex);
+    mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
+}
+
+}; // namespace android
\ No newline at end of file
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
new file mode 100644
index 0000000..c0cc9dc
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -0,0 +1,167 @@
+/*
+ ** 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_MULTIFILE_BLOB_CACHE_H
+#define ANDROID_MULTIFILE_BLOB_CACHE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <future>
+#include <map>
+#include <queue>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+
+struct MultifileHeader {
+    EGLsizeiANDROID keySize;
+    EGLsizeiANDROID valueSize;
+};
+
+struct MultifileEntryStats {
+    EGLsizeiANDROID valueSize;
+    size_t fileSize;
+    time_t accessTime;
+};
+
+struct MultifileHotCache {
+    int entryFd;
+    uint8_t* entryBuffer;
+    size_t entrySize;
+};
+
+enum class TaskCommand {
+    Invalid = 0,
+    WriteToDisk,
+    Exit,
+};
+
+class DeferredTask {
+public:
+    DeferredTask(TaskCommand command)
+          : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {}
+
+    TaskCommand getTaskCommand() { return mCommand; }
+
+    void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer,
+                         size_t bufferSize) {
+        mCommand = TaskCommand::WriteToDisk;
+        mEntryHash = entryHash;
+        mFullPath = std::move(fullPath);
+        mBuffer = buffer;
+        mBufferSize = bufferSize;
+    }
+
+    uint32_t getEntryHash() { return mEntryHash; }
+    std::string& getFullPath() { return mFullPath; }
+    uint8_t* getBuffer() { return mBuffer; }
+    size_t getBufferSize() { return mBufferSize; };
+
+private:
+    TaskCommand mCommand;
+
+    // Parameters for WriteToDisk
+    uint32_t mEntryHash;
+    std::string mFullPath;
+    uint8_t* mBuffer;
+    size_t mBufferSize;
+};
+
+class MultifileBlobCache {
+public:
+    MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir);
+    ~MultifileBlobCache();
+
+    void set(const void* key, EGLsizeiANDROID keySize, const void* value,
+             EGLsizeiANDROID valueSize);
+    EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value,
+                        EGLsizeiANDROID valueSize);
+
+    void finish();
+
+    size_t getTotalSize() const { return mTotalCacheSize; }
+
+private:
+    void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
+                    time_t accessTime);
+    bool contains(uint32_t entryHash) const;
+    bool removeEntry(uint32_t entryHash);
+    MultifileEntryStats getEntryStats(uint32_t entryHash);
+
+    size_t getFileSize(uint32_t entryHash);
+    size_t getValueSize(uint32_t entryHash);
+
+    void increaseTotalCacheSize(size_t fileSize);
+    void decreaseTotalCacheSize(size_t fileSize);
+
+    bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
+    bool removeFromHotCache(uint32_t entryHash);
+
+    void trimCache(size_t cacheByteLimit);
+    bool applyLRU(size_t cacheLimit);
+
+    bool mInitialized;
+    std::string mMultifileDirName;
+
+    std::unordered_set<uint32_t> mEntries;
+    std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
+    std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
+
+    size_t mMaxKeySize;
+    size_t mMaxValueSize;
+    size_t mMaxTotalSize;
+    size_t mTotalCacheSize;
+    size_t mHotCacheLimit;
+    size_t mHotCacheEntryLimit;
+    size_t mHotCacheSize;
+
+    // Below are the components used for deferred writes
+
+    // Track whether we have pending writes for an entry
+    std::multimap<uint32_t, uint8_t*> mDeferredWrites;
+
+    // Functions to work through tasks in the queue
+    void processTasks();
+    void processTasksImpl(bool* exitThread);
+    void processTask(DeferredTask& task);
+
+    // Used by main thread to create work for the worker thread
+    void queueTask(DeferredTask&& task);
+
+    // Used by main thread to wait for worker thread to complete all outstanding work.
+    void waitForWorkComplete();
+
+    std::thread mTaskThread;
+    std::queue<DeferredTask> mTasks;
+    std::mutex mWorkerMutex;
+
+    // This condition will block the worker thread until a task is queued
+    std::condition_variable mWorkAvailableCondition;
+
+    // This condition will block the main thread while the worker thread still has tasks
+    std::condition_variable mWorkerIdleCondition;
+
+    // This bool will track whether all tasks have been completed
+    bool mWorkerThreadIdle;
+};
+
+}; // namespace android
+
+#endif // ANDROID_MULTIFILE_BLOB_CACHE_H
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
new file mode 100644
index 0000000..1a55a4f
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -0,0 +1,200 @@
+/*
+ ** 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 "MultifileBlobCache.h"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <memory>
+
+namespace android {
+
+template <typename T>
+using sp = std::shared_ptr<T>;
+
+constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxPreloadSize = 8 * 1024;
+
+constexpr size_t kMaxKeySize = kMaxPreloadSize / 4;
+constexpr size_t kMaxValueSize = kMaxPreloadSize / 2;
+
+class MultifileBlobCacheTest : public ::testing::Test {
+protected:
+    virtual void SetUp() {
+        mTempFile.reset(new TemporaryFile());
+        mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0]));
+    }
+
+    virtual void TearDown() { mMBC.reset(); }
+
+    std::unique_ptr<TemporaryFile> mTempFile;
+    std::unique_ptr<MultifileBlobCache> mMBC;
+};
+
+TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
+    unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+    mMBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+    ASSERT_EQ('g', buf[2]);
+    ASSERT_EQ('h', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) {
+    unsigned char buf[2] = {0xee, 0xee};
+    mMBC->set("ab", 2, "cd", 2);
+    mMBC->set("ef", 2, "gh", 2);
+    ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+    ASSERT_EQ('c', buf[0]);
+    ASSERT_EQ('d', buf[1]);
+    ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2));
+    ASSERT_EQ('g', buf[0]);
+    ASSERT_EQ('h', buf[1]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) {
+    unsigned char buf[2] = {0xee, 0xee};
+    mMBC->set("ab", 2, "cd", 2);
+    ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+    ASSERT_EQ('c', buf[0]);
+    ASSERT_EQ('d', buf[1]);
+    // Use the same key, but different value
+    mMBC->set("ab", 2, "ef", 2);
+    ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) {
+    unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee};
+    mMBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4));
+    ASSERT_EQ(0xee, buf[0]);
+    ASSERT_EQ('e', buf[1]);
+    ASSERT_EQ('f', buf[2]);
+    ASSERT_EQ('g', buf[3]);
+    ASSERT_EQ('h', buf[4]);
+    ASSERT_EQ(0xee, buf[5]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
+    unsigned char buf[3] = {0xee, 0xee, 0xee};
+    mMBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3));
+    ASSERT_EQ(0xee, buf[0]);
+    ASSERT_EQ(0xee, buf[1]);
+    ASSERT_EQ(0xee, buf[2]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) {
+    mMBC->set("abcd", 4, "efgh", 4);
+    ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0));
+}
+
+TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) {
+    unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+    mMBC->set("abcd", 4, "efgh", 4);
+    mMBC->set("abcd", 4, "ijkl", 4);
+    ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
+    ASSERT_EQ('i', buf[0]);
+    ASSERT_EQ('j', buf[1]);
+    ASSERT_EQ('k', buf[2]);
+    ASSERT_EQ('l', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
+    unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee};
+    mMBC->set("abcd", 4, "efgh", 4);
+    mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
+    ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+    ASSERT_EQ('g', buf[2]);
+    ASSERT_EQ('h', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) {
+    char key[kMaxKeySize + 1];
+    unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+    for (int i = 0; i < kMaxKeySize + 1; i++) {
+        key[i] = 'a';
+    }
+    mMBC->set(key, kMaxKeySize + 1, "bbbb", 4);
+    ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4));
+    ASSERT_EQ(0xee, buf[0]);
+    ASSERT_EQ(0xee, buf[1]);
+    ASSERT_EQ(0xee, buf[2]);
+    ASSERT_EQ(0xee, buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) {
+    char buf[kMaxValueSize + 1];
+    for (int i = 0; i < kMaxValueSize + 1; i++) {
+        buf[i] = 'b';
+    }
+    mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
+    for (int i = 0; i < kMaxValueSize + 1; i++) {
+        buf[i] = 0xee;
+    }
+    ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1));
+    for (int i = 0; i < kMaxValueSize + 1; i++) {
+        SCOPED_TRACE(i);
+        ASSERT_EQ(0xee, buf[i]);
+    }
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) {
+    char key[kMaxKeySize];
+    unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+    for (int i = 0; i < kMaxKeySize; i++) {
+        key[i] = 'a';
+    }
+    mMBC->set(key, kMaxKeySize, "wxyz", 4);
+    ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4));
+    ASSERT_EQ('w', buf[0]);
+    ASSERT_EQ('x', buf[1]);
+    ASSERT_EQ('y', buf[2]);
+    ASSERT_EQ('z', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) {
+    char buf[kMaxValueSize];
+    for (int i = 0; i < kMaxValueSize; i++) {
+        buf[i] = 'b';
+    }
+    mMBC->set("abcd", 4, buf, kMaxValueSize);
+    for (int i = 0; i < kMaxValueSize; i++) {
+        buf[i] = 0xee;
+    }
+    mMBC->get("abcd", 4, buf, kMaxValueSize);
+    for (int i = 0; i < kMaxValueSize; i++) {
+        SCOPED_TRACE(i);
+        ASSERT_EQ('b', buf[i]);
+    }
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
+    unsigned char buf[1] = {0xee};
+    mMBC->set("x", 1, "y", 1);
+    ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1));
+    ASSERT_EQ('y', buf[0]);
+}
+
+} // namespace android
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 1e8a348..b00ee33 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -14,6 +14,8 @@
  ** limitations under the License.
  */
 
+// #define LOG_NDEBUG 0
+
 #include "egl_cache.h"
 
 #include <android-base/properties.h>
@@ -25,22 +27,19 @@
 #include <thread>
 
 #include "../egl_impl.h"
-#include "egl_cache_multifile.h"
 #include "egl_display.h"
 
 // Monolithic cache size limits.
-static const size_t maxKeySize = 12 * 1024;
-static const size_t maxValueSize = 64 * 1024;
-static const size_t maxTotalSize = 32 * 1024 * 1024;
+static const size_t kMaxMonolithicKeySize = 12 * 1024;
+static const size_t kMaxMonolithicValueSize = 64 * 1024;
+static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024;
 
 // The time in seconds to wait before saving newly inserted monolithic cache entries.
-static const unsigned int deferredSaveDelay = 4;
+static const unsigned int kDeferredMonolithicSaveDelay = 4;
 
-// Multifile cache size limit
-constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024;
-
-// Delay before cleaning up multifile cache entries
-static const unsigned int deferredMultifileCleanupDelaySeconds = 1;
+// Multifile cache size limits
+constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024;
+constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024;
 
 namespace android {
 
@@ -68,10 +67,7 @@
 // egl_cache_t definition
 //
 egl_cache_t::egl_cache_t()
-      : mInitialized(false),
-        mMultifileMode(false),
-        mCacheByteLimit(maxTotalSize),
-        mMultifileCleanupPending(false) {}
+      : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {}
 
 egl_cache_t::~egl_cache_t() {}
 
@@ -85,7 +81,7 @@
     std::lock_guard<std::mutex> lock(mMutex);
 
     egl_connection_t* const cnx = &gEGLImpl;
-    if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
+    if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
         const char* exts = display->disp.queryString.extensions;
         size_t bcExtLen = strlen(BC_EXT_STR);
         size_t extsLen = strlen(exts);
@@ -114,14 +110,36 @@
         }
     }
 
-    // Allow forcing monolithic cache for debug purposes
-    if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") {
-        ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\"");
+    // Check the device config to decide whether multifile should be used
+    if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) {
+        mMultifileMode = true;
+        ALOGV("Using multifile EGL blobcache");
+    }
+
+    // Allow forcing the mode for debug purposes
+    std::string mode = base::GetProperty("debug.egl.blobcache.multifile", "");
+    if (mode == "true") {
+        ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
+        mMultifileMode = true;
+    } else if (mode == "false") {
+        ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
         mMultifileMode = false;
     }
 
     if (mMultifileMode) {
-        mCacheByteLimit = kMultifileCacheByteLimit;
+        mCacheByteLimit = static_cast<size_t>(
+                base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit",
+                                                kMultifileCacheByteLimit));
+
+        // Check for a debug value
+        int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1);
+        if (debugCacheSize >= 0) {
+            ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit",
+                  mCacheByteLimit, debugCacheSize);
+            mCacheByteLimit = debugCacheSize;
+        }
+
+        ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit);
     }
 
     mInitialized = true;
@@ -133,10 +151,10 @@
         mBlobCache->writeToFile();
     }
     mBlobCache = nullptr;
-    if (mMultifileMode) {
-        checkMultifileCacheSize(mCacheByteLimit);
+    if (mMultifileBlobCache) {
+        mMultifileBlobCache->finish();
     }
-    mMultifileMode = false;
+    mMultifileBlobCache = nullptr;
     mInitialized = false;
 }
 
@@ -151,20 +169,8 @@
 
     if (mInitialized) {
         if (mMultifileMode) {
-            setBlobMultifile(key, keySize, value, valueSize, mFilename);
-
-            if (!mMultifileCleanupPending) {
-                mMultifileCleanupPending = true;
-                // Kick off a thread to cull cache files below limit
-                std::thread deferredMultifileCleanupThread([this]() {
-                    sleep(deferredMultifileCleanupDelaySeconds);
-                    std::lock_guard<std::mutex> lock(mMutex);
-                    // Check the size of cache and remove entries to stay under limit
-                    checkMultifileCacheSize(mCacheByteLimit);
-                    mMultifileCleanupPending = false;
-                });
-                deferredMultifileCleanupThread.detach();
-            }
+            MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
+            mbc->set(key, keySize, value, valueSize);
         } else {
             BlobCache* bc = getBlobCacheLocked();
             bc->set(key, keySize, value, valueSize);
@@ -172,7 +178,7 @@
             if (!mSavePending) {
                 mSavePending = true;
                 std::thread deferredSaveThread([this]() {
-                    sleep(deferredSaveDelay);
+                    sleep(kDeferredMonolithicSaveDelay);
                     std::lock_guard<std::mutex> lock(mMutex);
                     if (mInitialized && mBlobCache) {
                         mBlobCache->writeToFile();
@@ -196,15 +202,21 @@
 
     if (mInitialized) {
         if (mMultifileMode) {
-            return getBlobMultifile(key, keySize, value, valueSize, mFilename);
+            MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
+            return mbc->get(key, keySize, value, valueSize);
         } else {
             BlobCache* bc = getBlobCacheLocked();
             return bc->get(key, keySize, value, valueSize);
         }
     }
+
     return 0;
 }
 
+void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) {
+    mMultifileMode = (cacheMode == EGLCacheMode::Multifile);
+}
+
 void egl_cache_t::setCacheFilename(const char* filename) {
     std::lock_guard<std::mutex> lock(mMutex);
     mFilename = filename;
@@ -216,7 +228,7 @@
     if (!mMultifileMode) {
         // If we're not in multifile mode, ensure the cache limit is only being lowered,
         // not increasing above the hard coded platform limit
-        if (cacheByteLimit > maxTotalSize) {
+        if (cacheByteLimit > kMaxMonolithicTotalSize) {
             return;
         }
     }
@@ -226,8 +238,8 @@
 
 size_t egl_cache_t::getCacheSize() {
     std::lock_guard<std::mutex> lock(mMutex);
-    if (mMultifileMode) {
-        return getMultifileCacheSize();
+    if (mMultifileBlobCache) {
+        return mMultifileBlobCache->getTotalSize();
     }
     if (mBlobCache) {
         return mBlobCache->getSize();
@@ -237,9 +249,18 @@
 
 BlobCache* egl_cache_t::getBlobCacheLocked() {
     if (mBlobCache == nullptr) {
-        mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename));
+        mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize,
+                                           mCacheByteLimit, mFilename));
     }
     return mBlobCache.get();
 }
 
+MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() {
+    if (mMultifileBlobCache == nullptr) {
+        mMultifileBlobCache.reset(
+                new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename));
+    }
+    return mMultifileBlobCache.get();
+}
+
 }; // namespace android
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index 2dcd803..1399368 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -25,6 +25,7 @@
 #include <string>
 
 #include "FileBlobCache.h"
+#include "MultifileBlobCache.h"
 
 namespace android {
 
@@ -32,6 +33,11 @@
 
 class EGLAPI egl_cache_t {
 public:
+    enum class EGLCacheMode {
+        Monolithic,
+        Multifile,
+    };
+
     // get returns a pointer to the singleton egl_cache_t object.  This
     // singleton object will never be destroyed.
     static egl_cache_t* get();
@@ -64,6 +70,9 @@
     // cache contents from one program invocation to another.
     void setCacheFilename(const char* filename);
 
+    // Allow setting monolithic or multifile modes
+    void setCacheMode(EGLCacheMode cacheMode);
+
     // Allow the fixed cache limit to be overridden
     void setCacheLimit(int64_t cacheByteLimit);
 
@@ -85,6 +94,9 @@
     // possible.
     BlobCache* getBlobCacheLocked();
 
+    // Get or create the multifile blobcache
+    MultifileBlobCache* getMultifileBlobCacheLocked();
+
     // mInitialized indicates whether the egl_cache_t is in the initialized
     // state.  It is initialized to false at construction time, and gets set to
     // true when initialize is called.  It is set back to false when terminate
@@ -98,6 +110,9 @@
     // first time it's needed.
     std::unique_ptr<FileBlobCache> mBlobCache;
 
+    // The multifile version of blobcache allowing larger contents to be stored
+    std::unique_ptr<MultifileBlobCache> mMultifileBlobCache;
+
     // mFilename is the name of the file for storing cache contents in between
     // program invocations.  It is initialized to an empty string at
     // construction time, and can be set with the setCacheFilename method.  An
@@ -123,11 +138,7 @@
     bool mMultifileMode;
 
     // Cache limit
-    int64_t mCacheByteLimit;
-
-    // Whether we've kicked off a side thread that will check the multifile
-    // cache size and remove entries if needed.
-    bool mMultifileCleanupPending;
+    size_t mCacheByteLimit;
 };
 
 }; // namespace android
diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp
deleted file mode 100644
index 48e557f..0000000
--- a/opengl/libs/EGL/egl_cache_multifile.cpp
+++ /dev/null
@@ -1,343 +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.
- */
-
-// #define LOG_NDEBUG 0
-
-#include "egl_cache_multifile.h"
-
-#include <android-base/properties.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <log/log.h>
-#include <stdio.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <utime.h>
-
-#include <algorithm>
-#include <chrono>
-#include <fstream>
-#include <limits>
-#include <locale>
-#include <map>
-#include <sstream>
-#include <unordered_map>
-
-#include <utils/JenkinsHash.h>
-
-static std::string multifileDirName = "";
-
-using namespace std::literals;
-
-namespace {
-
-// Create a directory for tracking multiple files
-void setupMultifile(const std::string& baseDir) {
-    // If we've already set up the multifile dir in this base directory, we're done
-    if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) {
-        return;
-    }
-
-    // Otherwise, create it
-    multifileDirName = baseDir + ".multifile";
-    if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
-        ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno);
-    }
-}
-
-// Create a filename that is based on the hash of the key
-std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize,
-                                  const std::string& baseDir) {
-    // Hash the key into a string
-    std::stringstream keyName;
-    keyName << android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
-
-    // Build a filename using dir and hash
-    return baseDir + "/" + keyName.str();
-}
-
-// Determine file age based on stat modification time
-// Newer files have a higher age (time since epoch)
-time_t getFileAge(const std::string& filePath) {
-    struct stat st;
-    if (stat(filePath.c_str(), &st) == 0) {
-        ALOGD("getFileAge returning %" PRId64 " for file age", static_cast<uint64_t>(st.st_mtime));
-        return st.st_mtime;
-    } else {
-        ALOGW("Failed to stat %s", filePath.c_str());
-        return 0;
-    }
-}
-
-size_t getFileSize(const std::string& filePath) {
-    struct stat st;
-    if (stat(filePath.c_str(), &st) != 0) {
-        ALOGE("Unable to stat %s", filePath.c_str());
-        return 0;
-    }
-    return st.st_size;
-}
-
-// Walk through directory entries and track age and size
-// Then iterate through the entries, oldest first, and remove them until under the limit.
-// This will need to be updated if we move to a multilevel cache dir.
-bool applyLRU(size_t cacheLimit) {
-    // Build a multimap of files indexed by age.
-    // They will be automatically sorted smallest (oldest) to largest (newest)
-    std::multimap<time_t, std::string> agesToFiles;
-
-    // Map files to sizes
-    std::unordered_map<std::string, size_t> filesToSizes;
-
-    size_t totalCacheSize = 0;
-
-    DIR* dir;
-    struct dirent* entry;
-    if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
-        while ((entry = readdir(dir)) != nullptr) {
-            if (entry->d_name == "."s || entry->d_name == ".."s) {
-                continue;
-            }
-
-            // Look up each file age
-            std::string fullPath = multifileDirName + "/" + entry->d_name;
-            time_t fileAge = getFileAge(fullPath);
-
-            // Track the files, sorted by age
-            agesToFiles.insert(std::make_pair(fileAge, fullPath));
-
-            // Also track the size so we know how much room we have freed
-            size_t fileSize = getFileSize(fullPath);
-            filesToSizes[fullPath] = fileSize;
-            totalCacheSize += fileSize;
-        }
-        closedir(dir);
-    } else {
-        ALOGE("Unable to open filename: %s", multifileDirName.c_str());
-        return false;
-    }
-
-    if (totalCacheSize <= cacheLimit) {
-        // If LRU was called on a sufficiently small cache, no need to remove anything
-        return true;
-    }
-
-    // Walk through the map of files until we're under the cache size
-    for (const auto& cacheEntryIter : agesToFiles) {
-        time_t entryAge = cacheEntryIter.first;
-        const std::string entryPath = cacheEntryIter.second;
-
-        ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge);
-        if (std::remove(entryPath.c_str()) != 0) {
-            ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
-            return false;
-        }
-
-        totalCacheSize -= filesToSizes[entryPath];
-        if (totalCacheSize <= cacheLimit) {
-            // Success
-            ALOGV("Reduced cache to %zu", totalCacheSize);
-            return true;
-        } else {
-            ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize);
-        }
-    }
-
-    // Should never reach this return
-    return false;
-}
-
-} // namespace
-
-namespace android {
-
-void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
-                      EGLsizeiANDROID valueSize, const std::string& baseDir) {
-    if (baseDir.empty()) {
-        return;
-    }
-
-    setupMultifile(baseDir);
-    std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
-
-    ALOGD("Attempting to open filename for set: %s", filename.c_str());
-    std::ofstream outfile(filename, std::ofstream::binary);
-    if (outfile.fail()) {
-        ALOGW("Unable to open filename: %s", filename.c_str());
-        return;
-    }
-
-    // First write the key
-    outfile.write(static_cast<const char*>(key), keySize);
-    if (outfile.bad()) {
-        ALOGW("Unable to write key to filename: %s", filename.c_str());
-        outfile.close();
-        return;
-    }
-    ALOGD("Wrote %i bytes to out file for key", static_cast<int>(outfile.tellp()));
-
-    // Then write the value
-    outfile.write(static_cast<const char*>(value), valueSize);
-    if (outfile.bad()) {
-        ALOGW("Unable to write value to filename: %s", filename.c_str());
-        outfile.close();
-        return;
-    }
-    ALOGD("Wrote %i bytes to out file for full entry", static_cast<int>(outfile.tellp()));
-
-    outfile.close();
-}
-
-EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
-                                 EGLsizeiANDROID valueSize, const std::string& baseDir) {
-    if (baseDir.empty()) {
-        return 0;
-    }
-
-    setupMultifile(baseDir);
-    std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
-
-    // Open the hashed filename path
-    ALOGD("Attempting to open filename for get: %s", filename.c_str());
-    int fd = open(filename.c_str(), O_RDONLY);
-
-    // File doesn't exist, this is a MISS, return zero bytes read
-    if (fd == -1) {
-        ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(),
-              std::strerror(errno));
-        return 0;
-    }
-
-    ALOGD("Cache HIT - opened filename: %s", filename.c_str());
-
-    // Get the size of the file
-    size_t entrySize = getFileSize(filename);
-    if (keySize > entrySize) {
-        ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
-              "file",
-              keySize, entrySize);
-        close(fd);
-        return 0;
-    }
-
-    // Memory map the file
-    uint8_t* cacheEntry =
-            reinterpret_cast<uint8_t*>(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0));
-    if (cacheEntry == MAP_FAILED) {
-        ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
-        close(fd);
-        return 0;
-    }
-
-    // Compare the incoming key with our stored version (the beginning of the entry)
-    int compare = memcmp(cacheEntry, key, keySize);
-    if (compare != 0) {
-        ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
-        munmap(cacheEntry, entrySize);
-        close(fd);
-        return 0;
-    }
-
-    // Keys matched, so remaining cache is value size
-    size_t cachedValueSize = entrySize - keySize;
-
-    // Return actual value size if valueSize is not large enough
-    if (cachedValueSize > valueSize) {
-        ALOGD("Skipping file read, not enough room provided (valueSize): %lu, "
-              "returning required space as %zu",
-              valueSize, cachedValueSize);
-        munmap(cacheEntry, entrySize);
-        close(fd);
-        return cachedValueSize;
-    }
-
-    // Remaining entry following the key is the value
-    uint8_t* cachedValue = cacheEntry + keySize;
-    memcpy(value, cachedValue, cachedValueSize);
-    munmap(cacheEntry, entrySize);
-    close(fd);
-
-    ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str());
-    return cachedValueSize;
-}
-
-// Walk through the files in our flat directory, checking the size of each one.
-// Return the total size of normal files in the directory.
-// This will need to be updated if we move to a multilevel cache dir.
-size_t getMultifileCacheSize() {
-    if (multifileDirName.empty()) {
-        return 0;
-    }
-
-    DIR* dir;
-    struct dirent* entry;
-    size_t size = 0;
-
-    ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str());
-
-    if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
-        while ((entry = readdir(dir)) != nullptr) {
-            if (entry->d_name == "."s || entry->d_name == ".."s) {
-                continue;
-            }
-
-            // Add up the size of all files in the dir
-            std::string fullPath = multifileDirName + "/" + entry->d_name;
-            size += getFileSize(fullPath);
-        }
-        closedir(dir);
-    } else {
-        ALOGW("Unable to open filename: %s", multifileDirName.c_str());
-        return 0;
-    }
-
-    return size;
-}
-
-// When removing files, what fraction of the overall limit should be reached when removing files
-// A divisor of two will decrease the cache to 50%, four to 25% and so on
-constexpr uint32_t kCacheLimitDivisor = 2;
-
-// Calculate the cache size and remove old entries until under the limit
-void checkMultifileCacheSize(size_t cacheByteLimit) {
-    // Start with the value provided by egl_cache
-    size_t limit = cacheByteLimit;
-
-    // Check for a debug value
-    int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1);
-    if (debugCacheSize >= 0) {
-        ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit,
-              debugCacheSize);
-        limit = debugCacheSize;
-    }
-
-    // Tally up the initial amount of cache in use
-    size_t size = getMultifileCacheSize();
-    ALOGD("Multifile cache dir size: %zu", size);
-
-    // If size is larger than the threshold, remove files using LRU
-    if (size > limit) {
-        ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit);
-        if (!applyLRU(limit / kCacheLimitDivisor)) {
-            ALOGE("Error when clearing multifile shader cache");
-            return;
-        }
-    }
-    ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize());
-}
-
-}; // namespace android
\ No newline at end of file
diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h
deleted file mode 100644
index ee5fe81..0000000
--- a/opengl/libs/EGL/egl_cache_multifile.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.
- */
-
-#ifndef ANDROID_EGL_CACHE_MULTIFILE_H
-#define ANDROID_EGL_CACHE_MULTIFILE_H
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
-#include <string>
-
-namespace android {
-
-void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
-                      EGLsizeiANDROID valueSize, const std::string& baseDir);
-EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
-                                 EGLsizeiANDROID valueSize, const std::string& baseDir);
-size_t getMultifileCacheSize();
-void checkMultifileCacheSize(size_t cacheByteLimit);
-
-}; // namespace android
-
-#endif // ANDROID_EGL_CACHE_MULTIFILE_H
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index 265bec4..2b3e3a4 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -24,7 +24,7 @@
 #include <android-base/test_utils.h>
 
 #include "egl_cache.h"
-#include "egl_cache_multifile.h"
+#include "MultifileBlobCache.h"
 #include "egl_display.h"
 
 #include <memory>
@@ -33,12 +33,16 @@
 
 namespace android {
 
-class EGLCacheTest : public ::testing::Test {
+class EGLCacheTest : public ::testing::TestWithParam<egl_cache_t::EGLCacheMode> {
 protected:
     virtual void SetUp() {
-        mCache = egl_cache_t::get();
+        // Terminate to clean up any previous cache in this process
+        mCache->terminate();
+
         mTempFile.reset(new TemporaryFile());
         mCache->setCacheFilename(&mTempFile->path[0]);
+        mCache->setCacheLimit(1024);
+        mCache->setCacheMode(mCacheMode);
     }
 
     virtual void TearDown() {
@@ -49,11 +53,12 @@
 
     std::string getCachefileName();
 
-    egl_cache_t* mCache;
+    egl_cache_t* mCache = egl_cache_t::get();
     std::unique_ptr<TemporaryFile> mTempFile;
+    egl_cache_t::EGLCacheMode mCacheMode = GetParam();
 };
 
-TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) {
+TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) {
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->setBlob("abcd", 4, "efgh", 4);
     ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
@@ -63,7 +68,7 @@
     ASSERT_EQ(0xee, buf[3]);
 }
 
-TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) {
+TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) {
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
     mCache->setBlob("abcd", 4, "efgh", 4);
@@ -74,7 +79,7 @@
     ASSERT_EQ('h', buf[3]);
 }
 
-TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) {
+TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) {
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
     mCache->setBlob("abcd", 4, "efgh", 4);
@@ -86,7 +91,7 @@
     ASSERT_EQ(0xee, buf[3]);
 }
 
-TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) {
+TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) {
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
     mCache->setBlob("abcd", 4, "efgh", 4);
@@ -101,12 +106,12 @@
 
 std::string EGLCacheTest::getCachefileName() {
     // Return the monolithic filename unless we find the multifile dir
-    std::string cachefileName = &mTempFile->path[0];
-    std::string multifileDirName = cachefileName + ".multifile";
+    std::string cachePath = &mTempFile->path[0];
+    std::string multifileDirName = cachePath + ".multifile";
+    std::string cachefileName = "";
 
     struct stat info;
     if (stat(multifileDirName.c_str(), &info) == 0) {
-
         // Ensure we only have one file to manage
         int realFileCount = 0;
 
@@ -121,6 +126,9 @@
                 cachefileName = multifileDirName + "/" + entry->d_name;
                 realFileCount++;
             }
+        } else {
+            printf("Unable to open %s, error: %s\n",
+                   multifileDirName.c_str(), std::strerror(errno));
         }
 
         if (realFileCount != 1) {
@@ -128,14 +136,19 @@
             // violates test assumptions
             cachefileName = "";
         }
+    } else {
+        printf("Unable to stat %s, error: %s\n",
+               multifileDirName.c_str(), std::strerror(errno));
     }
 
     return cachefileName;
 }
 
-TEST_F(EGLCacheTest, ModifiedCacheMisses) {
-    // Turn this back on if multifile becomes the default
-    GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894";
+TEST_P(EGLCacheTest, ModifiedCacheMisses) {
+    // Skip if not in multifile mode
+    if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
+        GTEST_SKIP() << "Skipping test designed for multifile";
+    }
 
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
@@ -147,13 +160,13 @@
     ASSERT_EQ('g', buf[2]);
     ASSERT_EQ('h', buf[3]);
 
+    // Ensure the cache file is written to disk
+    mCache->terminate();
+
     // Depending on the cache mode, the file will be in different locations
     std::string cachefileName = getCachefileName();
     ASSERT_TRUE(cachefileName.length() > 0);
 
-    // Ensure the cache file is written to disk
-    mCache->terminate();
-
     // Stomp on the beginning of the cache file, breaking the key match
     const long stomp = 0xbadf00d;
     FILE *file = fopen(cachefileName.c_str(), "w");
@@ -164,14 +177,15 @@
     // Ensure no cache hit
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
     uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee };
-    ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4));
+    // getBlob may return junk for required size, but should not return a cache hit
+    mCache->getBlob("abcd", 4, buf2, 4);
     ASSERT_EQ(0xee, buf2[0]);
     ASSERT_EQ(0xee, buf2[1]);
     ASSERT_EQ(0xee, buf2[2]);
     ASSERT_EQ(0xee, buf2[3]);
 }
 
-TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
+TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
 
@@ -204,4 +218,8 @@
     ASSERT_LE(mCache->getCacheSize(), 4);
 }
 
+INSTANTIATE_TEST_CASE_P(MonolithicCacheTests,
+        EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic));
+INSTANTIATE_TEST_CASE_P(MultifileCacheTests,
+        EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile));
 }
diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp
index 02d62bf..a98b383 100644
--- a/services/inputflinger/InputProcessor.cpp
+++ b/services/inputflinger/InputProcessor.cpp
@@ -402,7 +402,7 @@
             { // acquire lock
                 std::scoped_lock threadLock(mLock);
                 mHalDeathRecipient =
-                        std::make_unique<ScopedDeathRecipient>(onBinderDied, this /*cookie*/);
+                        std::make_unique<ScopedDeathRecipient>(onBinderDied, /*cookie=*/this);
                 mHalDeathRecipient->linkToDeath(service->asBinder().get());
                 setMotionClassifierLocked(MotionClassifier::create(std::move(service)));
             } // release lock
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index a864cf8..2450235 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -73,6 +73,9 @@
     if (changes & CHANGE_ENABLED_STATE) {
         result += "ENABLED_STATE | ";
     }
+    if (changes & CHANGE_TOUCHPAD_SETTINGS) {
+        result += "TOUCHPAD_SETTINGS | ";
+    }
     if (changes & CHANGE_MUST_REOPEN) {
         result += "MUST_REOPEN | ";
     }
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index cacf30b..2a3dba7 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -42,9 +42,11 @@
       "options": [
         {
           "include-filter": "android.view.cts.input",
+          "include-filter": "android.view.cts.HoverTest",
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
           "include-filter": "android.view.cts.TooltipTest",
+          "include-filter": "android.view.cts.VelocityTrackerTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
@@ -131,9 +133,12 @@
       "name": "CtsViewTestCases",
       "options": [
         {
+          "include-filter": "android.view.cts.input",
+          "include-filter": "android.view.cts.HoverTest",
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
           "include-filter": "android.view.cts.TooltipTest",
+          "include-filter": "android.view.cts.VelocityTrackerTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 3d4dd7e..f03c837 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -141,7 +141,7 @@
                 ALOGE("Waited too long for consumer to produce an event, giving up");
                 break;
             }
-            result = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq,
+            result = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
                                         &event);
         }
         if (result != OK) {
@@ -308,13 +308,13 @@
     for (auto _ : state) {
         MotionEvent event = generateMotionEvent();
         // Send ACTION_DOWN
-        dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+        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,
+        dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                     INJECT_EVENT_TIMEOUT,
                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
@@ -344,7 +344,7 @@
 
     for (auto _ : state) {
         dispatcher.onWindowInfosChanged(windowInfos, displayInfos);
-        dispatcher.onWindowInfosChanged({} /*windowInfos*/, {} /*displayInfos*/);
+        dispatcher.onWindowInfosChanged(/*windowInfos=*/{}, /*displayInfos=*/{});
     }
     dispatcher.stop();
 }
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 512cb6e..83e6a60 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -16,7 +16,8 @@
 
 #pragma once
 
-#include <utils/BitSet.h>
+#include <input/Input.h>
+#include <bitset>
 #include <optional>
 
 namespace android {
@@ -29,6 +30,7 @@
         CANCEL_POINTER_EVENTS = 1,
         CANCEL_NON_POINTER_EVENTS = 2,
         CANCEL_FALLBACK_EVENTS = 3,
+        ftl_last = CANCEL_FALLBACK_EVENTS,
     };
 
     // The criterion to use to determine which events should be canceled.
@@ -47,7 +49,7 @@
     std::optional<int32_t> displayId = std::nullopt;
 
     // The specific pointers to cancel, or nullopt to cancel all pointer events
-    std::optional<BitSet32> pointerIds = std::nullopt;
+    std::optional<std::bitset<MAX_POINTER_ID + 1>> pointerIds = std::nullopt;
 
     CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {}
 };
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index dc9f02a..9d5bbbd 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -190,7 +190,7 @@
               pointerCount, MAX_POINTERS);
         return false;
     }
-    BitSet32 pointerIdBits;
+    std::bitset<MAX_POINTER_ID + 1> pointerIdBits;
     for (size_t i = 0; i < pointerCount; i++) {
         int32_t id = pointerProperties[i].id;
         if (id < 0 || id > MAX_POINTER_ID) {
@@ -198,11 +198,11 @@
                   MAX_POINTER_ID);
             return false;
         }
-        if (pointerIdBits.hasBit(id)) {
+        if (pointerIdBits.test(id)) {
             ALOGE("Motion event has duplicate pointer id %d", id);
             return false;
         }
-        pointerIdBits.markBit(id);
+        pointerIdBits.set(id);
     }
     return true;
 }
@@ -292,6 +292,17 @@
             first->applicationInfo.token == second->applicationInfo.token;
 }
 
+template <typename T>
+size_t firstMarkedBit(T set) {
+    // TODO: replace with std::countr_zero from <bit> when that's available
+    LOG_ALWAYS_FATAL_IF(set.none());
+    size_t i = 0;
+    while (!set.test(i)) {
+        i++;
+    }
+    return i;
+}
+
 std::unique_ptr<DispatchEntry> createDispatchEntry(
         const InputTarget& inputTarget, std::shared_ptr<EventEntry> eventEntry,
         ftl::Flags<InputTarget::Flags> inputTargetFlags) {
@@ -312,7 +323,7 @@
     // 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[inputTarget.pointerIds.firstMarkedBit()];
+            inputTarget.pointerTransforms[firstMarkedBit(inputTarget.pointerIds)];
     ui::Transform inverseFirstTransform = firstPointerTransform.inverse();
 
     // Iterate through all pointers in the event to normalize against the first.
@@ -591,7 +602,7 @@
             TouchedWindow touchedWindow;
             touchedWindow.windowHandle = oldWindow;
             touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT;
-            touchedWindow.pointerIds.markBit(pointerId);
+            touchedWindow.pointerIds.set(pointerId);
             out.push_back(touchedWindow);
         }
     }
@@ -608,7 +619,7 @@
             LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE);
             touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
         }
-        touchedWindow.pointerIds.markBit(pointerId);
+        touchedWindow.pointerIds.set(pointerId);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
             touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
         }
@@ -670,8 +681,7 @@
 
     while (!mConnectionsByToken.empty()) {
         sp<Connection> connection = mConnectionsByToken.begin()->second;
-        removeInputChannelLocked(connection->inputChannel->getConnectionToken(),
-                                 false /* notify */);
+        removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false);
     }
 }
 
@@ -1023,7 +1033,7 @@
     if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) {
         const int32_t displayId = motionEntry.displayId;
         const auto [x, y] = resolveTouchedPosition(motionEntry);
-        const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/);
+        const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0);
 
         auto [touchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
         if (touchedWindowHandle != nullptr &&
@@ -1166,7 +1176,7 @@
 
         if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
             addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
-                                  BitSet32(0), /*firstDownTimeInTarget=*/std::nullopt,
+                                  /*pointerIds=*/{}, /*firstDownTimeInTarget=*/std::nullopt,
                                   outsideTargets);
         }
     }
@@ -1663,7 +1673,7 @@
     std::vector<InputTarget> inputTargets;
     addWindowTargetLocked(focusedWindow,
                           InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS,
-                          BitSet32(0), getDownTime(*entry), inputTargets);
+                          /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
 
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1772,7 +1782,7 @@
             addWindowTargetLocked(focusedWindow,
                                   InputTarget::Flags::FOREGROUND |
                                           InputTarget::Flags::DISPATCH_AS_IS,
-                                  BitSet32(0), getDownTime(*entry), inputTargets);
+                                  /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
         }
     }
     if (injectionResult == InputEventInjectionResult::PENDING) {
@@ -2127,7 +2137,7 @@
 
         // Eventually, touchedWindow will contain the deviceId of each pointer that's currently
         // being sent there. For now, use deviceId from touch state.
-        if (entry.deviceId == touchState.deviceId && !touchedWindow.pointerIds.isEmpty()) {
+        if (entry.deviceId == touchState.deviceId && touchedWindow.pointerIds.any()) {
             return false;
         }
     }
@@ -2175,8 +2185,9 @@
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
 
     if (newGesture) {
-        bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
-        if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) {
+        // If pointers are already down, let's finish the current gesture and ignore the new events
+        // from another device.
+        if (switchedDevice && wasDown) {
             ALOGI("Dropping event because a pointer for a different device is already down "
                   "in display %" PRId32,
                   displayId);
@@ -2298,13 +2309,18 @@
             }
 
             // Update the temporary touch state.
-            BitSet32 pointerIds;
+            std::bitset<MAX_POINTER_ID + 1> pointerIds;
             if (!isHoverAction) {
-                pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+                pointerIds.set(entry.pointerProperties[pointerIndex].id);
             }
 
+            const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+                    maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
+
             tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
-                                             entry.eventTime);
+                                             isDownOrPointerDown
+                                                     ? std::make_optional(entry.eventTime)
+                                                     : std::nullopt);
 
             // 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
@@ -2312,8 +2328,7 @@
             // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
             // engine only supports touch events.  We would need to add a mechanism similar
             // to View.onGenericMotionEvent to enable wallpapers to handle these events.
-            if (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
-                maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+            if (isDownOrPointerDown) {
                 if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
                     windowHandle->getInfo()->inputConfig.test(
                             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
@@ -2333,14 +2348,22 @@
             }
         }
 
-        // If any existing window is pilfering pointers from newly added window, remove it
-        BitSet32 canceledPointers = BitSet32(0);
-        for (const TouchedWindow& window : tempTouchState.windows) {
-            if (window.isPilferingPointers) {
-                canceledPointers |= window.pointerIds;
+        // If a window is already pilfering some pointers, give it this new pointer as well and
+        // make it pilfering. This will prevent other non-spy windows from getting this pointer,
+        // which is a specific behaviour that we want.
+        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
+        for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+            if (touchedWindow.pointerIds.test(pointerId) &&
+                touchedWindow.pilferedPointerIds.count() > 0) {
+                // 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.pilferedPointerIds.set(pointerId);
             }
         }
-        tempTouchState.cancelPointersForNonPilferingWindows(canceledPointers);
+
+        // Restrict all pilfered pointers to the pilfering windows.
+        tempTouchState.cancelPointersForNonPilferingWindows();
     } else {
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
 
@@ -2360,7 +2383,7 @@
         if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
             tempTouchState.isSlippery()) {
             const auto [x, y] = resolveTouchedPosition(entry);
-            const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/);
+            const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
                     tempTouchState.getFirstForegroundWindowHandle();
             auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
@@ -2386,9 +2409,9 @@
                           newTouchedWindowHandle->getName().c_str(), displayId);
                 }
                 // Make a slippery exit from the old window.
-                BitSet32 pointerIds;
+                std::bitset<MAX_POINTER_ID + 1> pointerIds;
                 const int32_t pointerId = entry.pointerProperties[0].id;
-                pointerIds.markBit(pointerId);
+                pointerIds.set(pointerId);
 
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
@@ -2435,7 +2458,7 @@
                 if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
                     continue;
                 }
-                touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+                touchedWindow.pointerIds.set(entry.pointerProperties[pointerIndex].id);
             }
         }
     }
@@ -2509,6 +2532,11 @@
     // Success!  Output targets from the touch state.
     tempTouchState.clearWindowsWithoutPointers();
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
+        if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
+            // Windows with hovering pointers are getting persisted inside TouchState.
+            // Do not send this event to those windows.
+            continue;
+        }
         addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                               touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
                               targets);
@@ -2559,8 +2587,8 @@
 
         for (size_t i = 0; i < tempTouchState.windows.size();) {
             TouchedWindow& touchedWindow = tempTouchState.windows[i];
-            touchedWindow.pointerIds.clearBit(pointerId);
-            if (touchedWindow.pointerIds.isEmpty()) {
+            touchedWindow.pointerIds.reset(pointerId);
+            if (touchedWindow.pointerIds.none()) {
                 tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
                 continue;
             }
@@ -2591,7 +2619,7 @@
     constexpr bool isStylus = false;
 
     auto [dropWindow, _] =
-            findTouchedWindowAtLocked(displayId, x, y, isStylus, true /*ignoreDragWindow*/);
+            findTouchedWindowAtLocked(displayId, x, y, isStylus, /*ignoreDragWindow=*/true);
     if (dropWindow) {
         vec2 local = dropWindow->getInfo()->transform.transform(x, y);
         sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
@@ -2648,19 +2676,19 @@
             constexpr bool isStylus = false;
 
             auto [hoverWindowHandle, _] = findTouchedWindowAtLocked(entry.displayId, x, y, isStylus,
-                                                                    true /*ignoreDragWindow*/);
+                                                                    /*ignoreDragWindow=*/true);
             // enqueue drag exit if needed.
             if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
                 !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
                 if (mDragState->dragHoverWindowHandle != nullptr) {
-                    enqueueDragEventLocked(mDragState->dragHoverWindowHandle, true /*isExiting*/, x,
+                    enqueueDragEventLocked(mDragState->dragHoverWindowHandle, /*isExiting=*/true, x,
                                            y);
                 }
                 mDragState->dragHoverWindowHandle = hoverWindowHandle;
             }
             // enqueue drag location if needed.
             if (hoverWindowHandle != nullptr) {
-                enqueueDragEventLocked(hoverWindowHandle, false /*isExiting*/, x, y);
+                enqueueDragEventLocked(hoverWindowHandle, /*isExiting=*/false, x, y);
             }
             break;
         }
@@ -2685,7 +2713,7 @@
 
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                             ftl::Flags<InputTarget::Flags> targetFlags,
-                                            BitSet32 pointerIds,
+                                            std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                             std::optional<nsecs_t> firstDownTimeInTarget,
                                             std::vector<InputTarget>& inputTargets) const {
     std::vector<InputTarget>::iterator it =
@@ -2995,9 +3023,9 @@
     }
     if (DEBUG_DISPATCH_CYCLE) {
         ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, "
-              "globalScaleFactor=%f, pointerIds=0x%x %s",
+              "globalScaleFactor=%f, pointerIds=%s %s",
               connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(),
-              inputTarget.globalScaleFactor, inputTarget.pointerIds.value,
+              inputTarget.globalScaleFactor, bitsetToString(inputTarget.pointerIds).c_str(),
               inputTarget.getPointerInfoString().c_str());
     }
 
@@ -3165,6 +3193,9 @@
             }
 
             dispatchEntry->resolvedFlags = motionEntry.flags;
+            if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
+                dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_CANCELED;
+            }
             if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) {
                 dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
             }
@@ -3335,8 +3366,7 @@
                 // Don't apply window scale here since we don't want scale to affect raw
                 // coordinates. The scale will be sent back to the client and applied
                 // later when requesting relative coordinates.
-                scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */,
-                                      1 /* windowYScale */);
+                scaledCoords[i].scale(globalScaleFactor, /*windowXScale=*/1, /*windowYScale=*/1);
             }
             usingCoords = scaledCoords;
         }
@@ -3472,7 +3502,7 @@
                           "event to it, status=%s(%d)",
                           connection->getInputChannelName().c_str(), statusToString(status).c_str(),
                           status);
-                    abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+                    abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
                 } else {
                     // Pipe is full and we are waiting for the app to finish process some events
                     // before sending more events to it.
@@ -3487,7 +3517,7 @@
                       "status=%s(%d)",
                       connection->getInputChannelName().c_str(), statusToString(status).c_str(),
                       status);
-                abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+                abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
             }
             return;
         }
@@ -3732,9 +3762,9 @@
     }
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync "
-              "with reality: %s, mode=%d.",
+              "with reality: %s, mode=%s.",
               connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason,
-              options.mode);
+              ftl::enum_string(options.mode).c_str());
     }
 
     std::string reason = std::string("reason=").append(options.reason);
@@ -3868,8 +3898,9 @@
 }
 
 std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
-        const MotionEntry& originalMotionEntry, BitSet32 pointerIds, nsecs_t splitDownTime) {
-    ALOG_ASSERT(pointerIds.value != 0);
+        const MotionEntry& originalMotionEntry, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+        nsecs_t splitDownTime) {
+    ALOG_ASSERT(pointerIds.any());
 
     uint32_t splitPointerIndexMap[MAX_POINTERS];
     PointerProperties splitPointerProperties[MAX_POINTERS];
@@ -3883,7 +3914,7 @@
         const PointerProperties& pointerProperties =
                 originalMotionEntry.pointerProperties[originalPointerIndex];
         uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.hasBit(pointerId)) {
+        if (pointerIds.test(pointerId)) {
             splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
             splitPointerProperties[splitPointerCount].copyFrom(pointerProperties);
             splitPointerCoords[splitPointerCount].copyFrom(
@@ -3899,7 +3930,7 @@
         // 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 "
-              "we expected there to be %d pointers.  This probably means we received "
+              "we expected there to be %zu pointers.  This probably means we received "
               "a broken sequence of pointer ids from the input device.",
               splitPointerCount, pointerIds.count());
         return nullptr;
@@ -3913,7 +3944,7 @@
         const PointerProperties& pointerProperties =
                 originalMotionEntry.pointerProperties[originalPointerIndex];
         uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.hasBit(pointerId)) {
+        if (pointerIds.test(pointerId)) {
             if (pointerIds.count() == 1) {
                 // The first/last pointer went down/up.
                 action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
@@ -3939,8 +3970,8 @@
     if (action == AMOTION_EVENT_ACTION_DOWN) {
         LOG_ALWAYS_FATAL_IF(splitDownTime != originalMotionEntry.eventTime,
                             "Split motion event has mismatching downTime and eventTime for "
-                            "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64 "ms",
-                            originalMotionEntry.getDescription().c_str(), ns2ms(splitDownTime));
+                            "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64,
+                            originalMotionEntry.getDescription().c_str(), splitDownTime);
     }
 
     int32_t newId = mIdGenerator.nextId();
@@ -4155,6 +4186,17 @@
     bool needWake = false;
     { // acquire lock
         mLock.lock();
+        if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+            // Set the flag anyway if we already have an ongoing gesture. That would allow us to
+            // complete the processing of the current stroke.
+            const auto touchStateIt = mTouchStatesByDisplay.find(args->displayId);
+            if (touchStateIt != mTouchStatesByDisplay.end()) {
+                const TouchState& touchState = touchStateIt->second;
+                if (touchState.deviceId == args->deviceId && touchState.isDown()) {
+                    policyFlags |= POLICY_FLAG_PASS_TO_USER;
+                }
+            }
+        }
 
         if (shouldSendMotionToInputFilterLocked(args)) {
             ui::Transform displayTransform;
@@ -4224,7 +4266,7 @@
         // Just enqueue a new sensor event.
         std::unique_ptr<SensorEntry> newEntry =
                 std::make_unique<SensorEntry>(args->id, args->eventTime, args->deviceId,
-                                              args->source, 0 /* policyFlags*/, args->hwTimestamp,
+                                              args->source, /* policyFlags=*/0, args->hwTimestamp,
                                               args->sensorType, args->accuracy,
                                               args->accuracyChanged, args->values);
 
@@ -5224,7 +5266,7 @@
 
         // Erase old window.
         ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
-        BitSet32 pointerIds = touchedWindow->pointerIds;
+        std::bitset<MAX_POINTER_ID + 1> pointerIds = touchedWindow->pointerIds;
         sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
         state->removeWindowByToken(fromToken);
 
@@ -5245,7 +5287,7 @@
                 return false;
             }
             // Track the pointer id for drag window and generate the drag state.
-            const int32_t id = pointerIds.firstMarkedBit();
+            const size_t id = firstMarkedBit(pointerIds);
             mDragState = std::make_unique<DragState>(toWindowHandle, id);
         }
 
@@ -5618,7 +5660,7 @@
         const sp<IBinder>& token = serverChannel->getConnectionToken();
         int fd = serverChannel->getFd();
         sp<Connection> connection =
-                sp<Connection>::make(std::move(serverChannel), false /*monitor*/, mIdGenerator);
+                sp<Connection>::make(std::move(serverChannel), /*monitor=*/false, mIdGenerator);
 
         if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
             ALOGE("Created a new connection, but the token %p is already known", token.get());
@@ -5656,7 +5698,7 @@
         }
 
         sp<Connection> connection =
-                sp<Connection>::make(serverChannel, true /*monitor*/, mIdGenerator);
+                sp<Connection>::make(serverChannel, /*monitor=*/true, mIdGenerator);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
         const int fd = serverChannel->getFd();
 
@@ -5682,7 +5724,7 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
 
-        status_t status = removeInputChannelLocked(connectionToken, false /*notify*/);
+        status_t status = removeInputChannelLocked(connectionToken, /*notify=*/false);
         if (status) {
             return status;
         }
@@ -5745,7 +5787,7 @@
     }
 
     auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
-    if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.isEmpty()) {
+    if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.none()) {
         ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
               " Ignoring.");
         return BAD_VALUE;
@@ -5775,7 +5817,7 @@
 
     // Prevent the gesture from being sent to any other windows.
     // This only blocks relevant pointers to be sent to other windows
-    window.isPilferingPointers = true;
+    window.pilferedPointerIds |= window.pointerIds;
 
     state.cancelPointersForWindowsExcept(window.pointerIds, token);
     return OK;
@@ -6387,11 +6429,11 @@
             CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
                                        "focus left window");
             synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
-            enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason);
+            enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
         }
     }
     if (changes.newFocus) {
-        enqueueFocusEventLocked(changes.newFocus, true /*hasFocus*/, changes.reason);
+        enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason);
     }
 
     // If a window has pointer capture, then it must have focus. We need to ensure that this
@@ -6544,8 +6586,8 @@
                                          const sp<WindowInfoHandle>& newWindowHandle,
                                          TouchState& state, int32_t pointerId,
                                          std::vector<InputTarget>& targets) {
-    BitSet32 pointerIds;
-    pointerIds.markBit(pointerId);
+    std::bitset<MAX_POINTER_ID + 1> pointerIds;
+    pointerIds.set(pointerId);
     const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
     const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
@@ -6581,7 +6623,8 @@
                                              ftl::Flags<InputTarget::Flags> newTargetFlags,
                                              const sp<WindowInfoHandle> fromWindowHandle,
                                              const sp<WindowInfoHandle> toWindowHandle,
-                                             TouchState& state, const BitSet32& pointerIds) {
+                                             TouchState& state,
+                                             std::bitset<MAX_POINTER_ID + 1> pointerIds) {
     const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
             fromWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 81f8de8..b94858b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -46,6 +46,7 @@
 #include <utils/Looper.h>
 #include <utils/Timers.h>
 #include <utils/threads.h>
+#include <bitset>
 #include <condition_variable>
 #include <deque>
 #include <optional>
@@ -550,7 +551,8 @@
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
 
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                               ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
+                               ftl::Flags<InputTarget::Flags> targetFlags,
+                               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)
@@ -636,7 +638,8 @@
     // 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
     std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
-                                                  BitSet32 pointerIds, nsecs_t splitDownTime);
+                                                  std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                                  nsecs_t splitDownTime);
 
     // Reset and drop everything the dispatcher is doing.
     void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock);
@@ -703,7 +706,8 @@
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                                 const sp<android::gui::WindowInfoHandle> fromWindowHandle,
                                 const sp<android::gui::WindowInfoHandle> toWindowHandle,
-                                TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+                                TouchState& state, std::bitset<MAX_POINTER_ID + 1> pointerIds)
+            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 563868d..ad5a7fd 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -97,7 +97,7 @@
     switch (actionMasked) {
         case AMOTION_EVENT_ACTION_UP:
         case AMOTION_EVENT_ACTION_CANCEL: {
-            ssize_t index = findMotionMemento(entry, false /*hovering*/);
+            ssize_t index = findMotionMemento(entry, /*hovering=*/false);
             if (index >= 0) {
                 mMotionMementos.erase(mMotionMementos.begin() + index);
                 return true;
@@ -111,11 +111,11 @@
         }
 
         case AMOTION_EVENT_ACTION_DOWN: {
-            ssize_t index = findMotionMemento(entry, false /*hovering*/);
+            ssize_t index = findMotionMemento(entry, /*hovering=*/false);
             if (index >= 0) {
                 mMotionMementos.erase(mMotionMementos.begin() + index);
             }
-            addMotionMemento(entry, flags, false /*hovering*/);
+            addMotionMemento(entry, flags, /*hovering=*/false);
             return true;
         }
 
@@ -129,7 +129,7 @@
                 return true;
             }
 
-            ssize_t index = findMotionMemento(entry, false /*hovering*/);
+            ssize_t index = findMotionMemento(entry, /*hovering=*/false);
 
             if (entry.source & AINPUT_SOURCE_CLASS_JOYSTICK) {
                 // Joysticks can send MOVE events without a corresponding DOWN or UP. Since all
@@ -145,7 +145,7 @@
                         memento.setPointers(entry);
                     }
                 } else if (!entry.pointerCoords[0].isEmpty()) {
-                    addMotionMemento(entry, flags, false /*hovering*/);
+                    addMotionMemento(entry, flags, /*hovering=*/false);
                 }
 
                 // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP.
@@ -168,7 +168,7 @@
         }
 
         case AMOTION_EVENT_ACTION_HOVER_EXIT: {
-            ssize_t index = findMotionMemento(entry, true /*hovering*/);
+            ssize_t index = findMotionMemento(entry, /*hovering=*/true);
             if (index >= 0) {
                 mMotionMementos.erase(mMotionMementos.begin() + index);
                 return true;
@@ -183,11 +183,11 @@
 
         case AMOTION_EVENT_ACTION_HOVER_ENTER:
         case AMOTION_EVENT_ACTION_HOVER_MOVE: {
-            ssize_t index = findMotionMemento(entry, true /*hovering*/);
+            ssize_t index = findMotionMemento(entry, /*hovering=*/true);
             if (index >= 0) {
                 mMotionMementos.erase(mMotionMementos.begin() + index);
             }
-            addMotionMemento(entry, flags, true /*hovering*/);
+            addMotionMemento(entry, flags, /*hovering=*/true);
             return true;
         }
 
@@ -280,7 +280,7 @@
                                                memento.policyFlags, AKEY_EVENT_ACTION_UP,
                                                memento.flags | AKEY_EVENT_FLAG_CANCELED,
                                                memento.keyCode, memento.scanCode, memento.metaState,
-                                               0 /*repeatCount*/, memento.downTime));
+                                               /*repeatCount=*/0, memento.downTime));
         }
     }
 
@@ -289,13 +289,16 @@
             if (options.pointerIds == std::nullopt) {
                 const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
                                                         : AMOTION_EVENT_ACTION_CANCEL;
+                int32_t flags = memento.flags;
+                if (action == AMOTION_EVENT_ACTION_CANCEL) {
+                    flags |= AMOTION_EVENT_FLAG_CANCELED;
+                }
                 events.push_back(
                         std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
                                                       memento.deviceId, memento.source,
                                                       memento.displayId, memento.policyFlags,
-                                                      action, 0 /*actionButton*/, memento.flags,
-                                                      AMETA_NONE, 0 /*buttonState*/,
-                                                      MotionClassification::NONE,
+                                                      action, /*actionButton=*/0, flags, AMETA_NONE,
+                                                      /*buttonState=*/0, MotionClassification::NONE,
                                                       AMOTION_EVENT_EDGE_FLAG_NONE,
                                                       memento.xPrecision, memento.yPrecision,
                                                       memento.xCursorPosition,
@@ -356,8 +359,8 @@
                     std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
                                                   memento.deviceId, memento.source,
                                                   memento.displayId, memento.policyFlags, action,
-                                                  0 /*actionButton*/, memento.flags, AMETA_NONE,
-                                                  0 /*buttonState*/, MotionClassification::NONE,
+                                                  /*actionButton=*/0, memento.flags, AMETA_NONE,
+                                                  /*buttonState=*/0, MotionClassification::NONE,
                                                   AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                                   memento.yPrecision, memento.xCursorPosition,
                                                   memento.yCursorPosition, memento.downTime,
@@ -371,7 +374,8 @@
 }
 
 std::vector<std::unique_ptr<MotionEntry>> InputState::synthesizeCancelationEventsForPointers(
-        const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime) {
+        const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+        nsecs_t currentTime) {
     std::vector<std::unique_ptr<MotionEntry>> events;
     std::vector<uint32_t> canceledPointerIndices;
     std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
@@ -380,7 +384,7 @@
         uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
         pointerProperties[pointerIdx].copyFrom(memento.pointerProperties[pointerIdx]);
         pointerCoords[pointerIdx].copyFrom(memento.pointerCoords[pointerIdx]);
-        if (pointerIds.hasBit(pointerId)) {
+        if (pointerIds.test(pointerId)) {
             canceledPointerIndices.push_back(pointerIdx);
         }
     }
@@ -388,11 +392,15 @@
     if (canceledPointerIndices.size() == memento.pointerCount) {
         const int32_t action =
                 memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+        int32_t flags = memento.flags;
+        if (action == AMOTION_EVENT_ACTION_CANCEL) {
+            flags |= AMOTION_EVENT_FLAG_CANCELED;
+        }
         events.push_back(
                 std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime, memento.deviceId,
                                               memento.source, memento.displayId,
-                                              memento.policyFlags, action, 0 /*actionButton*/,
-                                              memento.flags, AMETA_NONE, 0 /*buttonState*/,
+                                              memento.policyFlags, action, /*actionButton=*/0,
+                                              flags, AMETA_NONE, /*buttonState=*/0,
                                               MotionClassification::NONE,
                                               AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                               memento.yPrecision, memento.xCursorPosition,
@@ -400,7 +408,7 @@
                                               memento.pointerCount, memento.pointerProperties,
                                               memento.pointerCoords));
     } else {
-        // If we aren't canceling all pointers, we need to generated ACTION_POINTER_UP with
+        // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
         // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
         // previously canceled pointers from PointerProperties and PointerCoords, and update
         // pointerCount appropriately. For convenience, sort the canceled pointer indices so that we
@@ -418,9 +426,9 @@
                     std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
                                                   memento.deviceId, memento.source,
                                                   memento.displayId, memento.policyFlags, action,
-                                                  0 /*actionButton*/,
+                                                  /*actionButton=*/0,
                                                   memento.flags | AMOTION_EVENT_FLAG_CANCELED,
-                                                  AMETA_NONE, 0 /*buttonState*/,
+                                                  AMETA_NONE, /*buttonState=*/0,
                                                   MotionClassification::NONE,
                                                   AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                                   memento.yPrecision, memento.xCursorPosition,
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 6ab48c9..42d8cc6 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -20,6 +20,7 @@
 #include "Entry.h"
 
 #include <utils/Timers.h>
+#include <bitset>
 
 namespace android {
 namespace inputdispatcher {
@@ -128,7 +129,8 @@
 
     // Synthesizes pointer cancel events for a particular set of pointers.
     std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers(
-            const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime);
+            const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+            nsecs_t currentTime);
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 2f39480..fc8b785 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -24,31 +24,34 @@
 
 namespace android::inputdispatcher {
 
-void InputTarget::addPointers(BitSet32 newPointerIds, const ui::Transform& transform) {
+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.isEmpty()) {
+    if (newPointerIds.none()) {
         setDefaultPointerTransform(transform);
         return;
     }
 
     // Ensure that the new set of pointers doesn't overlap with the current set of pointers.
-    ALOG_ASSERT((pointerIds & newPointerIds) == 0);
+    LOG_ALWAYS_FATAL_IF((pointerIds & newPointerIds).any());
 
     pointerIds |= newPointerIds;
-    while (!newPointerIds.isEmpty()) {
-        int32_t pointerId = newPointerIds.clearFirstMarkedBit();
-        pointerTransforms[pointerId] = transform;
+    for (size_t i = 0; i < newPointerIds.size(); i++) {
+        if (!newPointerIds.test(i)) {
+            continue;
+        }
+        pointerTransforms[i] = transform;
     }
 }
 
 void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) {
-    pointerIds.clear();
+    pointerIds.reset();
     pointerTransforms[0] = transform;
 }
 
 bool InputTarget::useDefaultPointerTransform() const {
-    return pointerIds.isEmpty();
+    return pointerIds.none();
 }
 
 const ui::Transform& InputTarget::getDefaultPointerTransform() const {
@@ -63,8 +66,8 @@
         return out;
     }
 
-    for (uint32_t i = pointerIds.firstMarkedBit(); i <= pointerIds.lastMarkedBit(); i++) {
-        if (!pointerIds.hasBit(i)) {
+    for (uint32_t i = 0; i < pointerIds.size(); i++) {
+        if (!pointerIds.test(i)) {
             continue;
         }
 
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 61b07fe..7b12f81 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -21,6 +21,7 @@
 #include <input/InputTransport.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
+#include <bitset>
 
 namespace android::inputdispatcher {
 
@@ -105,7 +106,7 @@
 
     // The subset of pointer ids to include in motion events dispatched to this input target
     // if FLAG_SPLIT is set.
-    BitSet32 pointerIds;
+    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;
@@ -113,7 +114,7 @@
     // Transform per pointerId.
     ui::Transform pointerTransforms[MAX_POINTERS];
 
-    void addPointers(BitSet32 pointerIds, const ui::Transform& transform);
+    void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform);
     void setDefaultPointerTransform(const ui::Transform& transform);
 
     /**
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
index 52f189c..2dd7d3f 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.cpp
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -148,7 +148,7 @@
 void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
     while (!mEventTimes.empty()) {
         const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin();
-        if (isMatureEvent(oldestEventTime, newEventTime /*now*/)) {
+        if (isMatureEvent(oldestEventTime, /*now=*/newEventTime)) {
             // Report and drop this event
             const auto it = mTimelines.find(oldestInputEventId);
             LOG_ALWAYS_FATAL_IF(it == mTimelines.end(),
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index ad37d02..4258471 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -33,7 +33,7 @@
 
 void TouchState::removeTouchedPointer(int32_t pointerId) {
     for (TouchedWindow& touchedWindow : windows) {
-        touchedWindow.pointerIds.clearBit(pointerId);
+        touchedWindow.removeTouchingPointer(pointerId);
     }
 }
 
@@ -41,7 +41,7 @@
         int32_t pointerId, const sp<android::gui::WindowInfoHandle>& windowHandle) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.pointerIds.clearBit(pointerId);
+            touchedWindow.removeTouchingPointer(pointerId);
             return;
         }
     }
@@ -55,13 +55,14 @@
 
 void TouchState::clearWindowsWithoutPointers() {
     std::erase_if(windows, [](const TouchedWindow& w) {
-        return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+        return w.pointerIds.none() && !w.hasHoveringPointers();
     });
 }
 
 void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
-                                   ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
-                                   std::optional<nsecs_t> eventTime) {
+                                   ftl::Flags<InputTarget::Flags> targetFlags,
+                                   std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                   std::optional<nsecs_t> firstDownTimeInTarget) {
     for (TouchedWindow& touchedWindow : windows) {
         // We do not compare windows by token here because two windows that share the same token
         // may have a different transform
@@ -73,9 +74,9 @@
             // 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 an pointer is down for
             // the window.
-            touchedWindow.pointerIds.value |= pointerIds.value;
+            touchedWindow.pointerIds |= pointerIds;
             if (!touchedWindow.firstDownTimeInTarget.has_value()) {
-                touchedWindow.firstDownTimeInTarget = eventTime;
+                touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;
             }
             return;
         }
@@ -84,7 +85,7 @@
     touchedWindow.windowHandle = windowHandle;
     touchedWindow.targetFlags = targetFlags;
     touchedWindow.pointerIds = pointerIds;
-    touchedWindow.firstDownTimeInTarget = eventTime;
+    touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;
     windows.push_back(touchedWindow);
 }
 
@@ -126,25 +127,44 @@
     }
 }
 
-void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds,
+void TouchState::cancelPointersForWindowsExcept(std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                                 const sp<IBinder>& token) {
-    if (pointerIds.isEmpty()) return;
+    if (pointerIds.none()) return;
     std::for_each(windows.begin(), windows.end(), [&pointerIds, &token](TouchedWindow& w) {
         if (w.windowHandle->getToken() != token) {
-            w.pointerIds &= BitSet32(~pointerIds.value);
+            w.pointerIds &= ~pointerIds;
         }
     });
-    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
+    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); });
 }
 
-void TouchState::cancelPointersForNonPilferingWindows(const BitSet32 pointerIds) {
-    if (pointerIds.isEmpty()) return;
-    std::for_each(windows.begin(), windows.end(), [&pointerIds](TouchedWindow& w) {
-        if (!w.isPilferingPointers) {
-            w.pointerIds &= BitSet32(~pointerIds.value);
-        }
+/**
+ * For any pointer that's being pilfered, remove it from all of the other windows that currently
+ * aren't pilfering it. For example, if we determined that pointer 1 is going to both window A and
+ * window B, but window A is currently pilfering pointer 1, then pointer 1 should not go to window
+ * B.
+ */
+void TouchState::cancelPointersForNonPilferingWindows() {
+    // First, find all pointers that are being pilfered, across all windows
+    std::bitset<MAX_POINTER_ID + 1> allPilferedPointerIds;
+    std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](const TouchedWindow& w) {
+        allPilferedPointerIds |= w.pilferedPointerIds;
     });
-    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
+
+    // Optimization: most of the time, pilfering does not occur
+    if (allPilferedPointerIds.none()) return;
+
+    // Now, remove all pointers from every window that's being pilfered by other windows.
+    // For example, if window A is pilfering pointer 1 (only), and window B is pilfering pointer 2
+    // (only), the remove pointer 2 from window A and pointer 1 from window B. Usually, the set of
+    // pilfered pointers will be disjoint across all windows, but there's no reason to cause that
+    // limitation here.
+    std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](TouchedWindow& w) {
+        std::bitset<MAX_POINTER_ID + 1> pilferedByOtherWindows =
+                w.pilferedPointerIds ^ allPilferedPointerIds;
+        w.pointerIds &= ~pilferedByOtherWindows;
+    });
+    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); });
 }
 
 sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
@@ -193,7 +213,7 @@
 
 bool TouchState::isDown() const {
     return std::any_of(windows.begin(), windows.end(),
-                       [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); });
+                       [](const TouchedWindow& window) { return window.pointerIds.any(); });
 }
 
 std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId,
@@ -212,7 +232,7 @@
         window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId);
     }
     std::erase_if(windows, [](const TouchedWindow& w) {
-        return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+        return w.pointerIds.none() && !w.hasHoveringPointers();
     });
 }
 
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 4025db8..6e965d8 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <bitset>
 #include <set>
 #include "TouchedWindow.h"
 
@@ -46,8 +47,9 @@
     void removeTouchedPointerFromWindow(int32_t pointerId,
                                         const sp<android::gui::WindowInfoHandle>& windowHandle);
     void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                           ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
-                           std::optional<nsecs_t> eventTime = std::nullopt);
+                           ftl::Flags<InputTarget::Flags> targetFlags,
+                           std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                           std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                     int32_t deviceId, int32_t hoveringPointerId);
     void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
@@ -56,10 +58,11 @@
     void filterNonAsIsTouchWindows();
 
     // Cancel pointers for current set of windows except the window with particular binder token.
-    void cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp<IBinder>& token);
+    void cancelPointersForWindowsExcept(std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                        const sp<IBinder>& token);
     // Cancel pointers for current set of non-pilfering windows i.e. windows with isPilferingWindow
     // set to false.
-    void cancelPointersForNonPilferingWindows(const BitSet32 pointerIds);
+    void cancelPointersForNonPilferingWindows();
 
     sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
     bool isSlippery() const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 3704edd..99c4769 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -29,6 +29,10 @@
     return !mHoveringPointerIdsByDevice.empty();
 }
 
+bool TouchedWindow::hasHoveringPointers(int32_t deviceId) const {
+    return mHoveringPointerIdsByDevice.find(deviceId) != mHoveringPointerIdsByDevice.end();
+}
+
 void TouchedWindow::clearHoveringPointers() {
     mHoveringPointerIdsByDevice.clear();
 }
@@ -46,6 +50,14 @@
     it->second.set(pointerId);
 }
 
+void TouchedWindow::removeTouchingPointer(int32_t pointerId) {
+    pointerIds.reset(pointerId);
+    pilferedPointerIds.reset(pointerId);
+    if (pointerIds.none()) {
+        firstDownTimeInTarget.reset();
+    }
+}
+
 void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) {
     const auto it = mHoveringPointerIdsByDevice.find(deviceId);
     if (it == mHoveringPointerIdsByDevice.end()) {
@@ -62,11 +74,11 @@
     std::string out;
     std::string hoveringPointers =
             dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString);
-    out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, "
-                        "mHoveringPointerIdsByDevice=%s\n",
-                        windowHandle->getName().c_str(), pointerIds.value,
+    out += StringPrintf("name='%s', pointerIds=%s, targetFlags=%s, firstDownTimeInTarget=%s, "
+                        "mHoveringPointerIdsByDevice=%s, pilferedPointerIds=%s\n",
+                        windowHandle->getName().c_str(), bitsetToString(pointerIds).c_str(),
                         targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(),
-                        hoveringPointers.c_str());
+                        hoveringPointers.c_str(), bitsetToString(pilferedPointerIds).c_str());
     return out;
 }
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index add6b61..aa2e9dd 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -30,22 +30,25 @@
 struct TouchedWindow {
     sp<gui::WindowInfoHandle> windowHandle;
     ftl::Flags<InputTarget::Flags> targetFlags;
-    BitSet32 pointerIds;
-    bool isPilferingPointers = false;
+    std::bitset<MAX_POINTER_ID + 1> pointerIds;
+    // The pointer ids of the pointers that this window is currently pilfering
+    std::bitset<MAX_POINTER_ID + 1> pilferedPointerIds;
     // Time at which the first action down occurred on this window.
     // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario.
     std::optional<nsecs_t> firstDownTimeInTarget;
 
     bool hasHoveringPointers() const;
+    bool hasHoveringPointers(int32_t deviceId) const;
 
     bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const;
     void addHoveringPointer(int32_t deviceId, int32_t pointerId);
     void removeHoveringPointer(int32_t deviceId, int32_t pointerId);
+    void removeTouchingPointer(int32_t pointerId);
     void clearHoveringPointers();
     std::string dump() const;
 
 private:
-    std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTERS>> mHoveringPointerIdsByDevice;
+    std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mHoveringPointerIdsByDevice;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index d55ab28..841c914 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -161,7 +161,7 @@
 struct InputReaderConfiguration {
     // Describes changes that have occurred.
     enum {
-        // The pointer speed changed.
+        // The mouse pointer speed changed.
         CHANGE_POINTER_SPEED = 1 << 0,
 
         // The pointer gesture control changed.
@@ -200,6 +200,9 @@
         // The stylus button reporting configurations has changed.
         CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12,
 
+        // The touchpad settings changed.
+        CHANGE_TOUCHPAD_SETTINGS = 1 << 13,
+
         // All devices must be reopened.
         CHANGE_MUST_REOPEN = 1 << 31,
     };
@@ -309,6 +312,20 @@
     // The latest request to enable or disable Pointer Capture.
     PointerCaptureRequest pointerCaptureRequest;
 
+    // The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest).
+    int32_t touchpadPointerSpeed;
+
+    // True to invert the touchpad scrolling direction, so that moving two fingers downwards on the
+    // touchpad scrolls the content upwards.
+    bool touchpadNaturalScrollingEnabled;
+
+    // True to enable tap-to-click on touchpads.
+    bool touchpadTapToClickEnabled;
+
+    // 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;
+
     // The set of currently disabled input devices.
     std::set<int32_t> disabledDevices;
 
@@ -316,6 +333,9 @@
     // stylus button state changes are reported through motion events.
     bool stylusButtonMotionEventsEnabled;
 
+    // True if a pointer icon should be shown for direct stylus pointers.
+    bool stylusPointerIconEnabled;
+
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
@@ -337,7 +357,12 @@
             pointerGestureZoomSpeedRatio(0.3f),
             showTouches(false),
             pointerCaptureRequest(),
-            stylusButtonMotionEventsEnabled(true) {}
+            touchpadPointerSpeed(0),
+            touchpadNaturalScrollingEnabled(true),
+            touchpadTapToClickEnabled(true),
+            touchpadRightClickZoneEnabled(false),
+            stylusButtonMotionEventsEnabled(true),
+            stylusPointerIconEnabled(false) {}
 
     static std::string changesToString(uint32_t changes);
 
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 7e0c1c7..9dbdd5a 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -79,8 +79,10 @@
         POINTER,
         // Show spots and a spot anchor in place of the mouse pointer.
         SPOT,
+        // Show the stylus hover pointer.
+        STYLUS_HOVER,
 
-        ftl_last = SPOT,
+        ftl_last = STYLUS_HOVER,
     };
 
     /* Sets the mode of the pointer controller. */
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index f7b38a1..d3d8021 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -220,7 +220,7 @@
  * directly from /dev.
  */
 static bool isV4lScanningEnabled() {
-    return property_get_bool("ro.input.video_enabled", true /* default_value */);
+    return property_get_bool("ro.input.video_enabled", /*default_value=*/true);
 }
 
 static nsecs_t processEventTimestamp(const struct input_event& event) {
@@ -1006,7 +1006,7 @@
     }
     int32_t outKeyCode;
     status_t mapKeyRes =
-            device->getKeyCharacterMap()->mapKey(scanCodes[0], 0 /*usageCode*/, &outKeyCode);
+            device->getKeyCharacterMap()->mapKey(scanCodes[0], /*usageCode=*/0, &outKeyCode);
     switch (mapKeyRes) {
         case OK:
             break;
@@ -2544,7 +2544,7 @@
 
     std::unique_ptr<Device> device =
             std::make_unique<Device>(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "<virtual>",
-                                     identifier, nullptr /*associatedDevice*/);
+                                     identifier, /*associatedDevice=*/nullptr);
     device->classes = InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY |
             InputDeviceClass::DPAD | InputDeviceClass::VIRTUAL;
     device->loadKeyMapLocked();
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index bd41fa5..c598c0a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -209,10 +209,10 @@
 
     // Touchscreens and touchpad devices.
     static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
-            sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false);
+            sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
     // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense
     // (ce6) touchpads, or at least load this setting from the IDC file.
-    const InputDeviceIdentifier& identifier = contextPtr->getDeviceIdentifier();
+    const InputDeviceIdentifier identifier = contextPtr->getDeviceIdentifier();
     const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c &&
             (identifier.product == 0x05c4 || identifier.product == 0x09cc ||
              identifier.product == 0x0ce6);
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index cedbacb..a380b5e 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -152,7 +152,7 @@
         return std::nullopt;
     }
 
-    return toArgb(brightness.value(), 0 /* red */, 0 /* green */, 0 /* blue */);
+    return toArgb(brightness.value(), /*red=*/0, /*green=*/0, /*blue=*/0);
 }
 
 std::optional<int32_t> PeripheralController::RgbLight::getLightColor() {
@@ -197,13 +197,12 @@
     }
     std::unordered_map<LightColor, int32_t> intensities = ret.value();
     // Get red, green, blue colors
-    int32_t color = toArgb(0 /* brightness */, intensities.at(LightColor::RED) /* red */,
-                           intensities.at(LightColor::GREEN) /* green */,
-                           intensities.at(LightColor::BLUE) /* blue */);
+    int32_t color = toArgb(/*brightness=*/0, intensities.at(LightColor::RED),
+                           intensities.at(LightColor::GREEN), intensities.at(LightColor::BLUE));
     // Get brightness
     std::optional<int32_t> brightness = getRawLightBrightness(rawId);
     if (brightness.has_value()) {
-        return toArgb(brightness.value() /* A */, 0, 0, 0) | color;
+        return toArgb(/*brightness=*/brightness.value(), 0, 0, 0) | color;
     }
     return std::nullopt;
 }
@@ -273,7 +272,7 @@
     for (const auto& [lightId, light] : mLights) {
         // Input device light doesn't support ordinal, always pass 1.
         InputDeviceLightInfo lightInfo(light->name, light->id, light->type, light->capabilityFlags,
-                                       1 /* ordinal */);
+                                       /*ordinal=*/1);
         deviceInfo->addLightInfo(lightInfo);
     }
 }
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 929bf18..d7f8b17 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -301,7 +301,7 @@
         case EV_SYN:
             switch (rawEvent->code) {
                 case SYN_REPORT:
-                    out += sync(rawEvent->when, rawEvent->readTime, false /*force*/);
+                    out += sync(rawEvent->when, rawEvent->readTime, /*force=*/false);
                     break;
             }
             break;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index d147d60..0361bc6 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -440,7 +440,7 @@
     for (size_t i = 0; i < n; i++) {
         out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when,
                                        systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource,
-                                       getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP,
+                                       getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP,
                                        AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED,
                                        mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
                                        mKeyDowns[i].downTime));
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 633efc6..33e72c7 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -202,10 +202,10 @@
             slotCount = MAX_SLOTS;
         }
         mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount,
-                                               true /*usingSlotsProtocol*/);
+                                               /*usingSlotsProtocol=*/true);
     } else {
         mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS,
-                                               false /*usingSlotsProtocol*/);
+                                               /*usingSlotsProtocol=*/false);
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index d81022f..f797bc9 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -208,10 +208,10 @@
     InputDeviceSensorInfo sensorInfo(identifier.name, std::to_string(identifier.vendor),
                                      identifier.version, sensorType,
                                      InputDeviceSensorAccuracy::ACCURACY_HIGH,
-                                     axis.max /* maxRange */, axis.scale /* resolution */,
-                                     0.0f /* power */, 0 /* minDelay */,
-                                     0 /* fifoReservedEventCount */, 0 /* fifoMaxEventCount */,
-                                     ftl::enum_string(sensorType), 0 /* maxDelay */, 0 /* flags */,
+                                     /*maxRange=*/axis.max, /*resolution=*/axis.scale,
+                                     /*power=*/0.0f, /*minDelay=*/0,
+                                     /*fifoReservedEventCount=*/0, /*fifoMaxEventCount=*/0,
+                                     ftl::enum_string(sensorType), /*maxDelay=*/0, /*flags=*/0,
                                      getDeviceId());
 
     std::string prefix = "sensor." + ftl::enum_string(sensorType);
@@ -277,7 +277,7 @@
                         Axis& axis = pair.second;
                         axis.currentValue = axis.newValue;
                     }
-                    out += sync(rawEvent->when, false /*force*/);
+                    out += sync(rawEvent->when, /*force=*/false);
                     break;
             }
             break;
@@ -344,7 +344,7 @@
               maxBatchReportLatency.count());
     }
 
-    if (!setSensorEnabled(sensorType, true /* enabled */)) {
+    if (!setSensorEnabled(sensorType, /*enabled=*/true)) {
         return false;
     }
 
@@ -367,7 +367,7 @@
         ALOGD("Disable Sensor %s", ftl::enum_string(sensorType).c_str());
     }
 
-    if (!setSensorEnabled(sensorType, false /* enabled */)) {
+    if (!setSensorEnabled(sensorType, /*enabled=*/false)) {
         return;
     }
 
@@ -413,9 +413,9 @@
             out.push_back(NotifySensorArgs(getContext()->getNextId(), when, getDeviceId(),
                                            AINPUT_SOURCE_SENSOR, sensorType,
                                            sensor.sensorInfo.accuracy,
-                                           sensor.accuracy !=
-                                                   sensor.sensorInfo.accuracy /* accuracyChanged */,
-                                           timestamp /* hwTimestamp */, values));
+                                           /*accuracyChanged=*/sensor.accuracy !=
+                                                   sensor.sensorInfo.accuracy,
+                                           /*hwTimestamp=*/timestamp, values));
             sensor.lastSampleTimeNs = timestamp;
             sensor.accuracy = sensor.sensorInfo.accuracy;
         }
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index c9101ca..c4564a4 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -59,7 +59,7 @@
     std::list<NotifyArgs> out;
     if (mUpdatedSwitchMask) {
         uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask;
-        out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, 0 /*policyFlags*/,
+        out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, /*policyFlags=*/0,
                                        updatedSwitchValues, mUpdatedSwitchMask));
 
         mUpdatedSwitchMask = 0;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 0c57628..31fdac9 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -977,12 +977,18 @@
         mOrientedRanges.clear();
     }
 
-    // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to
-    // preserve the cursor position.
-    if (mDeviceMode == DeviceMode::POINTER ||
-        (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
-        (mParameters.deviceType == Parameters::DeviceType::POINTER &&
-         mConfig.pointerCaptureRequest.enable)) {
+    // 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());
         }
@@ -1455,7 +1461,7 @@
               next.rawPointerData.hoveringIdBits.value);
     }
 
-    out += processRawTouches(false /*timeout*/);
+    out += processRawTouches(/*timeout=*/false);
     return out;
 }
 
@@ -1749,11 +1755,11 @@
         if (mPointerUsage == PointerUsage::GESTURES) {
             // Since this is a synthetic event, we can consider its latency to be zero
             const nsecs_t readTime = when;
-            out += dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/);
+            out += dispatchPointerGestures(when, readTime, /*policyFlags=*/0, /*isTimeout=*/true);
         }
     } else if (mDeviceMode == DeviceMode::DIRECT) {
         if (mExternalStylusFusionTimeout <= when) {
-            out += processRawTouches(true /*timeout*/);
+            out += processRawTouches(/*timeout=*/true);
         } else if (mExternalStylusFusionTimeout != LLONG_MAX) {
             getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
         }
@@ -1772,7 +1778,7 @@
         // - Only the button state, which is not reported through a specific pointer, has changed.
         // Go ahead and dispatch now that we have fresh stylus data.
         mExternalStylusDataPending = true;
-        out += processRawTouches(false /*timeout*/);
+        out += processRawTouches(/*timeout=*/false);
     }
     return out;
 }
@@ -2150,6 +2156,53 @@
     return out;
 }
 
+std::list<NotifyArgs> TouchInputMapper::dispatchGestureButtonRelease(nsecs_t when,
+                                                                     uint32_t policyFlags,
+                                                                     BitSet32 idBits,
+                                                                     nsecs_t readTime) {
+    std::list<NotifyArgs> out;
+    BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState);
+    const int32_t metaState = getContext()->getGlobalMetaState();
+    int32_t buttonState = mLastCookedState.buttonState;
+
+    while (!releasedButtons.isEmpty()) {
+        int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit());
+        buttonState &= ~actionButton;
+        out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
+                                     AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
+                                     metaState, buttonState, 0,
+                                     mPointerGesture.lastGestureProperties,
+                                     mPointerGesture.lastGestureCoords,
+                                     mPointerGesture.lastGestureIdToIndex, idBits, -1,
+                                     mOrientedXPrecision, mOrientedYPrecision,
+                                     mPointerGesture.downTime, MotionClassification::NONE));
+    }
+    return out;
+}
+
+std::list<NotifyArgs> TouchInputMapper::dispatchGestureButtonPress(nsecs_t when,
+                                                                   uint32_t policyFlags,
+                                                                   BitSet32 idBits,
+                                                                   nsecs_t readTime) {
+    std::list<NotifyArgs> out;
+    BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState);
+    const int32_t metaState = getContext()->getGlobalMetaState();
+    int32_t buttonState = mLastCookedState.buttonState;
+
+    while (!pressedButtons.isEmpty()) {
+        int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit());
+        buttonState |= actionButton;
+        out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
+                                     AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState,
+                                     buttonState, 0, mPointerGesture.currentGestureProperties,
+                                     mPointerGesture.currentGestureCoords,
+                                     mPointerGesture.currentGestureIdToIndex, idBits, -1,
+                                     mOrientedXPrecision, mOrientedYPrecision,
+                                     mPointerGesture.downTime, MotionClassification::NONE));
+    }
+    return out;
+}
+
 const BitSet32& TouchInputMapper::findActiveIdBits(const CookedPointerData& cookedPointerData) {
     if (!cookedPointerData.touchingIdBits.isEmpty()) {
         return cookedPointerData.touchingIdBits;
@@ -2373,7 +2426,7 @@
 
     switch (mPointerUsage) {
         case PointerUsage::GESTURES:
-            out += dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/);
+            out += dispatchPointerGestures(when, readTime, policyFlags, /*isTimeout=*/false);
             break;
         case PointerUsage::STYLUS:
             out += dispatchPointerStylus(when, readTime, policyFlags);
@@ -2534,8 +2587,13 @@
                         dispatchedGestureIdBits.value & ~mPointerGesture.currentGestureIdBits.value;
             }
             while (!upGestureIdBits.isEmpty()) {
-                uint32_t id = upGestureIdBits.clearFirstMarkedBit();
-
+                if (((mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 ||
+                     (mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) &&
+                    mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) {
+                    out += dispatchGestureButtonRelease(when, policyFlags, dispatchedGestureIdBits,
+                                                        readTime);
+                }
+                const uint32_t id = upGestureIdBits.clearFirstMarkedBit();
                 out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
                                              AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState,
                                              buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
@@ -2580,6 +2638,12 @@
                                          mPointerGesture.currentGestureIdToIndex,
                                          dispatchedGestureIdBits, id, 0, 0,
                                          mPointerGesture.downTime, classification));
+            if (((buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 ||
+                 (buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) &&
+                mPointerGesture.currentGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) {
+                out += dispatchGestureButtonPress(when, policyFlags, dispatchedGestureIdBits,
+                                                  readTime);
+            }
         }
     }
 
@@ -3650,6 +3714,14 @@
     return out;
 }
 
+static bool isStylusEvent(uint32_t source, int32_t action, const PointerProperties* properties) {
+    if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+        return false;
+    }
+    const auto actionIndex = action >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+    return isStylusToolType(properties[actionIndex].toolType);
+}
+
 NotifyMotionArgs TouchInputMapper::dispatchMotion(
         nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
         int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
@@ -3691,12 +3763,35 @@
             ALOG_ASSERT(false);
         }
     }
+
+    const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
+    const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
+            mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, 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;
+        }
+    }
+
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mDeviceMode == DeviceMode::POINTER) {
         mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
     }
-    const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const int32_t deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
     std::for_each(frames.begin(), frames.end(),
@@ -3710,8 +3805,8 @@
 
 std::list<NotifyArgs> TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    out += abortPointerUsage(when, readTime, 0 /*policyFlags*/);
-    out += abortTouches(when, readTime, 0 /* policyFlags*/);
+    out += abortPointerUsage(when, readTime, /*policyFlags=*/0);
+    out += abortTouches(when, readTime, /* policyFlags=*/0);
     return out;
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 87deb39..7b464ef 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -735,6 +735,14 @@
                                                               uint32_t policyFlags);
     [[nodiscard]] std::list<NotifyArgs> dispatchButtonPress(nsecs_t when, nsecs_t readTime,
                                                             uint32_t policyFlags);
+    [[nodiscard]] std::list<NotifyArgs> dispatchGestureButtonPress(nsecs_t when,
+                                                                   uint32_t policyFlags,
+                                                                   BitSet32 idBits,
+                                                                   nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> dispatchGestureButtonRelease(nsecs_t when,
+                                                                     uint32_t policyFlags,
+                                                                     BitSet32 idBits,
+                                                                     nsecs_t readTime);
     const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData);
     void cookPointerData();
     [[nodiscard]] std::list<NotifyArgs> abortTouches(nsecs_t when, nsecs_t readTime,
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index b6313a1..9f32311 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -146,6 +146,18 @@
         }
         mGestureConverter.setOrientation(orientation);
     }
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS)) {
+        // TODO(b/265798483): load an Android-specific acceleration curve instead of mapping to one
+        // of five ChromeOS curves.
+        const int pointerSensitivity = (config->touchpadPointerSpeed + 7) / 3 + 1;
+        mPropertyProvider.getProperty("Pointer Sensitivity").setIntValues({pointerSensitivity});
+        mPropertyProvider.getProperty("Invert Scrolling")
+                .setBoolValues({config->touchpadNaturalScrollingEnabled});
+        mPropertyProvider.getProperty("Tap Enable")
+                .setBoolValues({config->touchpadTapToClickEnabled});
+        mPropertyProvider.getProperty("Button Right Click Zone Enable")
+                .setBoolValues({config->touchpadRightClickZoneEnabled});
+    }
     return {};
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 6601702..6b84f32 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -115,7 +115,7 @@
     int32_t keyCode, metaState;
     uint32_t flags;
     if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(),
-                              0 /*metaState*/, &keyCode, &metaState, &flags) != OK) {
+                              /*metaState=*/0, &keyCode, &metaState, &flags) != OK) {
         return;
     }
     switch (keyCode) {
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 561b1f8..d636d44 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -219,11 +219,11 @@
     float deltaY = gesture.details.scroll.dy;
     rotateDelta(mOrientation, &deltaX, &deltaY);
 
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) - deltaX);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) - deltaY);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) + deltaX);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + deltaY);
     // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET.
-    coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, gesture.details.scroll.dx);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, gesture.details.scroll.dy);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, -gesture.details.scroll.dx);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
                                  mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
                                  mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index bb8a30e..30c1719 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -205,6 +205,10 @@
     mConfig.stylusButtonMotionEventsEnabled = enabled;
 }
 
+void FakeInputReaderPolicy::setStylusPointerIconEnabled(bool enabled) {
+    mConfig.stylusPointerIconEnabled = enabled;
+}
+
 void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
     *outConfig = mConfig;
 }
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 9ec3217..28ac505 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -76,6 +76,7 @@
     float getPointerGestureZoomSpeedRatio();
     void setVelocityControlParams(const VelocityControlParameters& params);
     void setStylusButtonMotionEventsEnabled(bool enabled);
+    void setStylusPointerIconEnabled(bool enabled);
 
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index ab7879f..28dad95 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -65,6 +65,10 @@
     ASSERT_NEAR(y, actualY, 1);
 }
 
+bool FakePointerController::isPointerShown() {
+    return mIsPointerShown;
+}
+
 bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
                                       float* outMaxY) const {
     *outMinX = mMinX;
@@ -83,6 +87,13 @@
     if (mY > mMaxY) mY = mMaxY;
 }
 
+void FakePointerController::fade(Transition) {
+    mIsPointerShown = false;
+}
+void FakePointerController::unfade(Transition) {
+    mIsPointerShown = true;
+}
+
 void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
                                      int32_t displayId) {
     std::vector<int32_t> newSpots;
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index d10cbcd..dd56e65 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -39,12 +39,13 @@
     void setDisplayViewport(const DisplayViewport& viewport) override;
 
     void assertPosition(float x, float y);
+    bool isPointerShown();
 
 private:
     bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override;
     void move(float deltaX, float deltaY) override;
-    void fade(Transition) override {}
-    void unfade(Transition) override {}
+    void fade(Transition) override;
+    void unfade(Transition) override;
     void setPresentation(Presentation) override {}
     void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
                   int32_t displayId) override;
@@ -55,6 +56,7 @@
     float mX{0}, mY{0};
     int32_t mButtonState{0};
     int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    bool mIsPointerShown{false};
 
     std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
 };
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 5d5cf9c..ccdb37a 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -55,11 +55,11 @@
     sp<IBinder> unfocusableWindowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
     windows.push_back(sp<FakeWindowHandle>::make("Focusable", focusableWindowToken,
-                                                 true /* focusable */, true /* visible */));
+                                                 /*focusable=*/true, /*visible=*/true));
     windows.push_back(sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken,
-                                                 true /* focusable */, false /* visible */));
+                                                 /*focusable=*/true, /*visible=*/false));
     windows.push_back(sp<FakeWindowHandle>::make("unfocusable", unfocusableWindowToken,
-                                                 false /* focusable */, true /* visible */));
+                                                 /*focusable=*/false, /*visible=*/true));
 
     // focusable window can get focused
     FocusRequest request;
@@ -88,7 +88,7 @@
     sp<IBinder> focusableWindowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
     windows.push_back(sp<FakeWindowHandle>::make("Focusable", focusableWindowToken,
-                                                 true /* focusable */, true /* visible */));
+                                                 /*focusable=*/true, /*visible=*/true));
 
     FocusRequest request;
     request.displayId = 42;
@@ -114,19 +114,19 @@
     sp<IBinder> unfocusableWindowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
     windows.push_back(sp<FakeWindowHandle>::make("Mirror1", focusableWindowToken,
-                                                 true /* focusable */, true /* visible */));
+                                                 /*focusable=*/true, /*visible=*/true));
     windows.push_back(sp<FakeWindowHandle>::make("Mirror1", focusableWindowToken,
-                                                 true /* focusable */, true /* visible */));
+                                                 /*focusable=*/true, /*visible=*/true));
 
     windows.push_back(sp<FakeWindowHandle>::make("Mirror2Visible", invisibleWindowToken,
-                                                 true /* focusable */, true /* visible */));
+                                                 /*focusable=*/true, /*visible=*/true));
     windows.push_back(sp<FakeWindowHandle>::make("Mirror2Invisible", invisibleWindowToken,
-                                                 true /* focusable */, false /* visible */));
+                                                 /*focusable=*/true, /*visible=*/false));
 
     windows.push_back(sp<FakeWindowHandle>::make("Mirror3Focusable", unfocusableWindowToken,
-                                                 true /* focusable */, true /* visible */));
+                                                 /*focusable=*/true, /*visible=*/true));
     windows.push_back(sp<FakeWindowHandle>::make("Mirror3Unfocusable", unfocusableWindowToken,
-                                                 false /* focusable */, true /* visible */));
+                                                 /*focusable=*/false, /*visible=*/true));
 
     // mirrored window can get focused
     FocusRequest request;
@@ -152,8 +152,8 @@
     sp<IBinder> focusableWindowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make("Focusable", focusableWindowToken, true /* focusable */,
-                                       true /* visible */);
+            sp<FakeWindowHandle>::make("Focusable", focusableWindowToken, /*focusable=*/true,
+                                       /*visible=*/true);
     windows.push_back(window);
 
     // focusable window can get focused
@@ -176,8 +176,8 @@
     std::vector<sp<WindowInfoHandle>> windows;
 
     sp<FakeWindowHandle> invisibleWindow =
-            sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken, true /* focusable */,
-                                       false /* visible */);
+            sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken, /*focusable=*/true,
+                                       /*visible=*/false);
     windows.push_back(invisibleWindow);
 
     // invisible window cannot get focused
@@ -200,8 +200,8 @@
     std::vector<sp<WindowInfoHandle>> windows;
 
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make("Test Window", windowToken, false /* focusable */,
-                                       true /* visible */);
+            sp<FakeWindowHandle>::make("Test Window", windowToken, /*focusable=*/false,
+                                       /*visible=*/true);
     windows.push_back(window);
 
     // non-focusable window cannot get focused
@@ -242,13 +242,13 @@
     std::vector<sp<WindowInfoHandle>> windows;
 
     sp<FakeWindowHandle> hostWindow =
-            sp<FakeWindowHandle>::make("Host Window", hostWindowToken, true /* focusable */,
-                                       true /* visible */);
+            sp<FakeWindowHandle>::make("Host Window", hostWindowToken, /*focusable=*/true,
+                                       /*visible=*/true);
     windows.push_back(hostWindow);
     sp<IBinder> embeddedWindowToken = sp<BBinder>::make();
     sp<FakeWindowHandle> embeddedWindow =
-            sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, true /* focusable */,
-                                       true /* visible */);
+            sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, /*focusable=*/true,
+                                       /*visible=*/true);
     windows.push_back(embeddedWindow);
 
     FocusRequest request;
@@ -293,8 +293,8 @@
     std::vector<sp<WindowInfoHandle>> windows;
 
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make("Test Window", windowToken, true /* focusable */,
-                                       true /* visible */);
+            sp<FakeWindowHandle>::make("Test Window", windowToken, /*focusable=*/true,
+                                       /*visible=*/true);
     windows.push_back(window);
 
     FocusRequest request;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 36a39bb..9c624ba 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -244,7 +244,7 @@
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
 
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+    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());
 
@@ -261,7 +261,7 @@
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                       WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
 
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+    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()),
@@ -289,7 +289,7 @@
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
 
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+    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());
 
@@ -306,7 +306,7 @@
                       WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                       WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
 
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+    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()),
@@ -332,10 +332,10 @@
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
 
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+    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);
+    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,
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 3abe43a..e71cdce 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -52,12 +52,14 @@
 
 // An arbitrary device id.
 static constexpr int32_t DEVICE_ID = 1;
+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;
 
 static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE;
+static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
 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 =
@@ -139,18 +141,26 @@
     return arg.getDownTime() == downTime;
 }
 
+MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
+    return arg.getDisplayId() == displayId;
+}
+
 MATCHER_P(WithSource, source, "InputEvent with specified source") {
     *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
                      << inputEventSourceToString(arg.getSource());
     return arg.getSource() == source;
 }
 
+MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
+    return arg.getFlags() == flags;
+}
+
 MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") {
     if (arg.getPointerCount() != 1) {
         *result_listener << "Expected 1 pointer, got " << arg.getPointerCount();
         return false;
     }
-    return arg.getX(0 /*pointerIndex*/) == x && arg.getY(0 /*pointerIndex*/) == y;
+    return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y;
 }
 
 MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") {
@@ -536,7 +546,7 @@
         /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
          * essentially a passthrough for notifySwitch.
          */
-        mLastNotifySwitch = NotifySwitchArgs(1 /*id*/, when, policyFlags, switchValues, switchMask);
+        mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask);
     }
 
     void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
@@ -625,7 +635,7 @@
                      /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
                      ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject key events with undefined action.";
 
@@ -634,7 +644,7 @@
                      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,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject key events with ACTION_MULTIPLE.";
 }
@@ -664,7 +674,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with undefined action.";
 
@@ -676,7 +686,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with pointer down index too large.";
 
@@ -688,7 +698,7 @@
                      identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with pointer down index too small.";
 
@@ -700,7 +710,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with pointer up index too large.";
 
@@ -712,7 +722,7 @@
                      identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with pointer up index too small.";
 
@@ -724,7 +734,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 0, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with 0 pointers.";
 
@@ -735,7 +745,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with more than MAX_POINTERS pointers.";
 
@@ -748,7 +758,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with pointer ids less than 0.";
 
@@ -760,7 +770,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
 
@@ -774,7 +784,7 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 2, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+              mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
             << "Should reject motion events with duplicate pointer ids.";
 }
@@ -783,7 +793,7 @@
 
 TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) {
     constexpr nsecs_t eventTime = 20;
-    NotifyConfigurationChangedArgs args(10 /*id*/, eventTime);
+    NotifyConfigurationChangedArgs args(/*id=*/10, eventTime);
     mDispatcher->notifyConfigurationChanged(&args);
     ASSERT_TRUE(mDispatcher->waitForIdle());
 
@@ -791,8 +801,8 @@
 }
 
 TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
-    NotifySwitchArgs args(10 /*id*/, 20 /*eventTime*/, 0 /*policyFlags*/, 1 /*switchValues*/,
-                          2 /*switchMask*/);
+    NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1,
+                          /*switchMask=*/2);
     mDispatcher->notifySwitch(&args);
 
     // InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener
@@ -853,7 +863,7 @@
         std::chrono::time_point start = std::chrono::steady_clock::now();
         status_t status = WOULD_BLOCK;
         while (status == WOULD_BLOCK) {
-            status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq,
+            status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
                                         &event);
             std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
             if (elapsed > 100ms) {
@@ -944,6 +954,28 @@
         }
     }
 
+    MotionEvent* consumeMotion() {
+        InputEvent* event = consume();
+
+        if (event == nullptr) {
+            ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
+            return nullptr;
+        }
+
+        if (event->getType() != AINPUT_EVENT_TYPE_MOTION) {
+            ADD_FAILURE() << mName << " expected a MotionEvent, got "
+                          << inputEventTypeToString(event->getType()) << " 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();
         ASSERT_NE(nullptr, event) << mName.c_str()
@@ -1196,14 +1228,14 @@
 
     void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                              int32_t expectedFlags = 0) {
-        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId,
-                     expectedFlags);
+        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) {
-        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId,
-                     expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                 WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
     }
 
     void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
@@ -1375,8 +1407,8 @@
 
     // Define a valid key down event.
     event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, displayId,
-                     INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE,
-                     repeatCount, currentTime, currentTime);
+                     INVALID_HMAC, action, /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, repeatCount,
+                     currentTime, currentTime);
 
     if (!allowKeyRepeat) {
         policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
@@ -1387,7 +1419,7 @@
 
 static InputEventInjectionResult injectKeyDown(const std::unique_ptr<InputDispatcher>& dispatcher,
                                                int32_t displayId = ADISPLAY_ID_NONE) {
-    return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId);
+    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
@@ -1395,14 +1427,14 @@
 // has to be woken up to process the repeating key.
 static InputEventInjectionResult injectKeyDownNoRepeat(
         const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) {
-    return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId,
+    return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId,
                      InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT,
-                     /* allowKeyRepeat */ false);
+                     /*allowKeyRepeat=*/false);
 }
 
 static InputEventInjectionResult injectKeyUp(const std::unique_ptr<InputDispatcher>& dispatcher,
                                              int32_t displayId = ADISPLAY_ID_NONE) {
-    return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /* repeatCount */ 0, displayId);
+    return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId);
 }
 
 class PointerBuilder {
@@ -1438,6 +1470,17 @@
         mAction = action;
         mSource = source;
         mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mDownTime = mEventTime;
+    }
+
+    MotionEventBuilder& deviceId(int32_t deviceId) {
+        mDeviceId = deviceId;
+        return *this;
+    }
+
+    MotionEventBuilder& downTime(nsecs_t downTime) {
+        mDownTime = downTime;
+        return *this;
     }
 
     MotionEventBuilder& eventTime(nsecs_t eventTime) {
@@ -1498,11 +1541,11 @@
 
         MotionEvent event;
         ui::Transform identityTransform;
-        event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC,
+        event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
                          mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
                          mButtonState, MotionClassification::NONE, identityTransform,
                          /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
-                         mRawYCursorPosition, identityTransform, mEventTime, mEventTime,
+                         mRawYCursorPosition, identityTransform, mDownTime, mEventTime,
                          mPointers.size(), pointerProperties.data(), pointerCoords.data());
 
         return event;
@@ -1510,7 +1553,9 @@
 
 private:
     int32_t mAction;
+    int32_t mDeviceId = DEVICE_ID;
     int32_t mSource;
+    nsecs_t mDownTime;
     nsecs_t mEventTime;
     int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
     int32_t mActionButton{0};
@@ -1522,6 +1567,113 @@
     std::vector<PointerBuilder> mPointers;
 };
 
+class MotionArgsBuilder {
+public:
+    MotionArgsBuilder(int32_t action, int32_t source) {
+        mAction = action;
+        mSource = source;
+        mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mDownTime = mEventTime;
+    }
+
+    MotionArgsBuilder& deviceId(int32_t deviceId) {
+        mDeviceId = deviceId;
+        return *this;
+    }
+
+    MotionArgsBuilder& downTime(nsecs_t downTime) {
+        mDownTime = downTime;
+        return *this;
+    }
+
+    MotionArgsBuilder& eventTime(nsecs_t eventTime) {
+        mEventTime = eventTime;
+        return *this;
+    }
+
+    MotionArgsBuilder& displayId(int32_t displayId) {
+        mDisplayId = displayId;
+        return *this;
+    }
+
+    MotionArgsBuilder& policyFlags(int32_t policyFlags) {
+        mPolicyFlags = policyFlags;
+        return *this;
+    }
+
+    MotionArgsBuilder& actionButton(int32_t actionButton) {
+        mActionButton = actionButton;
+        return *this;
+    }
+
+    MotionArgsBuilder& buttonState(int32_t buttonState) {
+        mButtonState = buttonState;
+        return *this;
+    }
+
+    MotionArgsBuilder& rawXCursorPosition(float rawXCursorPosition) {
+        mRawXCursorPosition = rawXCursorPosition;
+        return *this;
+    }
+
+    MotionArgsBuilder& rawYCursorPosition(float rawYCursorPosition) {
+        mRawYCursorPosition = rawYCursorPosition;
+        return *this;
+    }
+
+    MotionArgsBuilder& pointer(PointerBuilder pointer) {
+        mPointers.push_back(pointer);
+        return *this;
+    }
+
+    MotionArgsBuilder& addFlag(uint32_t flags) {
+        mFlags |= flags;
+        return *this;
+    }
+
+    NotifyMotionArgs build() {
+        std::vector<PointerProperties> pointerProperties;
+        std::vector<PointerCoords> pointerCoords;
+        for (const PointerBuilder& pointer : mPointers) {
+            pointerProperties.push_back(pointer.buildProperties());
+            pointerCoords.push_back(pointer.buildCoords());
+        }
+
+        // Set mouse cursor position for the most common cases to avoid boilerplate.
+        if (mSource == AINPUT_SOURCE_MOUSE &&
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
+            mPointers.size() == 1) {
+            mRawXCursorPosition = pointerCoords[0].getX();
+            mRawYCursorPosition = pointerCoords[0].getY();
+        }
+
+        NotifyMotionArgs args(InputEvent::nextId(), mEventTime, /*readTime=*/mEventTime, mDeviceId,
+                              mSource, mDisplayId, mPolicyFlags, mAction, mActionButton, mFlags,
+                              AMETA_NONE, mButtonState, MotionClassification::NONE, /*edgeFlags=*/0,
+                              mPointers.size(), pointerProperties.data(), pointerCoords.data(),
+                              /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
+                              mRawYCursorPosition, mDownTime, /*videoFrames=*/{});
+
+        return args;
+    }
+
+private:
+    int32_t mAction;
+    int32_t mDeviceId = DEVICE_ID;
+    int32_t mSource;
+    nsecs_t mDownTime;
+    nsecs_t mEventTime;
+    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    int32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
+    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};
+
+    std::vector<PointerBuilder> mPointers;
+};
+
 static InputEventInjectionResult injectMotionEvent(
         const std::unique_ptr<InputDispatcher>& dispatcher, const MotionEvent& event,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
@@ -1545,7 +1697,7 @@
                                 .eventTime(eventTime)
                                 .rawXCursorPosition(cursorPosition.x)
                                 .rawYCursorPosition(cursorPosition.y)
-                                .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER)
                                                  .x(position.x)
                                                  .y(position.y))
                                 .build();
@@ -1570,7 +1722,7 @@
 static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/* id */ 0, currentTime, 0 /*readTime*/, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+    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);
 
@@ -1599,7 +1751,7 @@
 
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid motion event.
-    NotifyMotionArgs args(/* id */ 0, currentTime, 0 /*readTime*/, DEVICE_ID, source, displayId,
+    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,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
@@ -1620,7 +1772,7 @@
 
 static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(
         const PointerCaptureRequest& request) {
-    return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), request);
+    return NotifyPointerCaptureChangedArgs(/*id=*/0, systemTime(SYSTEM_TIME_MONOTONIC), request);
 }
 
 /**
@@ -1850,32 +2002,24 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(100))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(150)
-                                     .y(150))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */);
-    wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+    foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
+    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
                                               expectedWallpaperFlags);
 
     const MotionEvent secondFingerUpEvent =
             MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(100))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(150)
-                                     .y(150))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
@@ -1934,12 +2078,8 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(100))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(300)
-                                     .y(100))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -1949,7 +2089,7 @@
     leftWindow->consumeMotionMove();
     // Since the touch is split, right window gets ACTION_DOWN
     rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
                                               expectedWallpaperFlags);
 
     // Now, leftWindow, which received the first finger, disappears.
@@ -1963,12 +2103,8 @@
     const MotionEvent secondFingerMoveEvent =
             MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(100))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(310)
-                                     .y(110))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(310).y(110))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
@@ -2026,6 +2162,385 @@
 }
 
 /**
+ * The policy typically sets POLICY_FLAG_PASS_TO_USER to the events. But when the display is not
+ * interactive, it might stop sending this flag.
+ * In this test, we check that if the policy stops sending this flag mid-gesture, we still ensure
+ * to have a consistent input stream.
+ *
+ * Test procedure:
+ * DOWN -> POINTER_DOWN -> (stop sending POLICY_FLAG_PASS_TO_USER) -> CANCEL.
+ * DOWN (new gesture).
+ *
+ * In the bad implementation, we could potentially drop the CANCEL event, and get an inconsistent
+ * state in the dispatcher. This would cause the final DOWN event to not be delivered to the app.
+ *
+ * We technically just need a single window here, but we are using two windows (spy on top and a
+ * regular window below) to emulate the actual situation where it happens on the device.
+ */
+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);
+    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);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+    const int32_t touchDeviceId = 4;
+    NotifyMotionArgs args;
+
+    // Two pointers down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .policyFlags(DEFAULT_POLICY_FLAGS)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .build()));
+
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .policyFlags(DEFAULT_POLICY_FLAGS)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
+                           .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    spyWindow->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+    // Cancel the current gesture. Send the cancel without the default policy flags.
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .policyFlags(0)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
+                           .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL));
+
+    // We don't need to reset the device to reproduce the issue, but the reset event typically
+    // follows, so we keep it here to model the actual listener behaviour more closely.
+    NotifyDeviceResetArgs resetArgs;
+    resetArgs.id = 1; // arbitrary id
+    resetArgs.eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    resetArgs.deviceId = touchDeviceId;
+    mDispatcher->notifyDeviceReset(&resetArgs);
+
+    // Start new gesture
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .policyFlags(DEFAULT_POLICY_FLAGS)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // No more events
+    spyWindow->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
+ * 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) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+    // 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
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .deviceId(mouseDeviceId)
+                                        .downTime(baseTime + 10)
+                                        .eventTime(baseTime + 20)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(100))
+                                        .build()));
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // .. to the left window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .deviceId(mouseDeviceId)
+                                        .downTime(baseTime + 10)
+                                        .eventTime(baseTime + 30)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_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
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .downTime(baseTime + 40)
+                                        .eventTime(baseTime + 40)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // release tap
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .downTime(baseTime + 40)
+                                        .eventTime(baseTime + 50)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // Tap the window on the right
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .downTime(baseTime + 60)
+                                        .eventTime(baseTime + 60)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(300)
+                                                         .y(100))
+                                        .build()));
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // release tap
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .downTime(baseTime + 60)
+                                        .eventTime(baseTime + 70)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(300)
+                                                         .y(100))
+                                        .build()));
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // No more events
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * 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 inject 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(InputDispatcherTest, HoverTapAndSplitTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+
+    const int32_t mouseDeviceId = 6;
+    const int32_t touchDeviceId = 4;
+    // Hover over the left window. Keep the cursor there.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .deviceId(mouseDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(50)
+                                                         .y(50))
+                                        .build()));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // Tap on left window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // First finger down on right window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(300)
+                                                         .y(100))
+                                        .build()));
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // Second finger down on the left window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(300)
+                                                         .y(100))
+                                        .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_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(InputDispatcherTest, StylusHoverAndTouchTap) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+    // Start hovering with stylus
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                   AINPUT_SOURCE_STYLUS)
+                                        .deviceId(stylusDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                         .x(50)
+                                                         .y(50))
+                                        .build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // Finger down on the window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // Try to continue hovering with stylus. Since we are already down, injection should fail
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_STYLUS)
+                                        .deviceId(stylusDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                         .x(50)
+                                                         .y(50))
+                                        .build()));
+    // No event should be sent. This event should be ignored because a pointer from another device
+    // is already down.
+
+    // Lift up the finger
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(touchDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // Now that the touch is gone, stylus hovering should start working again
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_STYLUS)
+                                        .deviceId(stylusDeviceId)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                         .x(50)
+                                                         .y(50))
+                                        .build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+    // No more events
+    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.
@@ -2277,6 +2792,124 @@
     spyWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_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);
+    window->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+
+    // Send mouse cursor to the window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+
+    // Move mouse cursor
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(110)
+                                                         .y(110))
+                                        .build()));
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                        WithSource(AINPUT_SOURCE_MOUSE)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                        WithSource(AINPUT_SOURCE_MOUSE)));
+    // Touch down on the window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(200)
+                                                         .y(200))
+                                        .build()));
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                        WithSource(AINPUT_SOURCE_MOUSE)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_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(AMOTION_EVENT_ACTION_CANCEL),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Touch UP on the window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(200)
+                                                         .y(200))
+                                        .build()));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_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
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(250)
+                                                         .y(250))
+                                        .build()));
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                        WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Touch UP on the window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .deviceId(SECOND_DEVICE_ID)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(250)
+                                                         .y(250))
+                                        .build()));
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                        WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    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) {
@@ -2521,7 +3154,7 @@
 
     // When device reset happens, that key stream should be terminated with FLAG_CANCELED
     // on the app side.
-    NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID);
+    NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
     mDispatcher->notifyDeviceReset(&args);
     window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
                          AKEY_EVENT_FLAG_CANCELED);
@@ -2544,10 +3177,10 @@
 
     // When device reset happens, that motion stream should be terminated with ACTION_CANCEL
     // on the app side.
-    NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID);
+    NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
     mDispatcher->notifyDeviceReset(&args);
-    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
-                         0 /*expectedFlags*/);
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
 
 TEST_F(InputDispatcherTest, InterceptKeyByPolicy) {
@@ -2726,10 +3359,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(-30)
-                                     .y(-50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-30).y(-50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -2846,7 +3477,7 @@
     MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                 .displayId(ADISPLAY_ID_DEFAULT)
                                 .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                                .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER)
                                                  .x(untransformedPoint.x)
                                                  .y(untransformedPoint.y))
                                 .build();
@@ -3130,7 +3761,7 @@
                                  [&](const std::unique_ptr<InputDispatcher>& dispatcher,
                                      sp<IBinder> from, sp<IBinder> to) {
                                      return dispatcher->transferTouchFocus(from, to,
-                                                                           false /*isDragAndDrop*/);
+                                                                           /*isDragAndDrop=*/false);
                                  }));
 
 TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) {
@@ -3503,7 +4134,7 @@
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
     graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
 
-    window->sendTimeline(1 /*inputEventId*/, graphicsTimeline);
+    window->sendTimeline(/*inputEventId=*/1, graphicsTimeline);
     window->assertNoEvents();
     mDispatcher->waitForIdle();
 }
@@ -3544,15 +4175,17 @@
     }
 
     void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver->consumeMotionEvent(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                      WithDisplayId(expectedDisplayId),
+                      WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
     }
 
     void consumeMotionPointerDown(int32_t pointerIdx) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
         mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, ADISPLAY_ID_DEFAULT,
-                                     0 /*expectedFlags*/);
+                                     /*expectedFlags=*/0);
     }
 
     MotionEvent* consumeMotion() {
@@ -3719,7 +4352,7 @@
 
     mDispatcher->notifyMotion(&motionArgs);
     window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
-                         0 /*expectedFlags*/);
+                         /*expectedFlags=*/0);
 }
 
 /**
@@ -3740,35 +4373,35 @@
     SCOPED_TRACE("Check default value of touch mode");
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
-    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     SCOPED_TRACE("Remove the window to trigger focus loss");
     window->setFocusable(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-    window->consumeFocusEvent(false /*hasFocus*/, true /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/true);
 
     SCOPED_TRACE("Disable touch mode");
     mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid,
-                                true /*hasPermission*/, ADISPLAY_ID_DEFAULT);
+                                /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
     window->consumeTouchModeEvent(false);
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
-    window->consumeFocusEvent(true /*hasFocus*/, false /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/false);
 
     SCOPED_TRACE("Remove the window to trigger focus loss");
     window->setFocusable(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-    window->consumeFocusEvent(false /*hasFocus*/, false /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/false);
 
     SCOPED_TRACE("Enable touch mode again");
     mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid,
-                                true /*hasPermission*/, ADISPLAY_ID_DEFAULT);
+                                /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
     window->consumeTouchModeEvent(true);
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
-    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     window->assertNoEvents();
 }
@@ -3784,7 +4417,7 @@
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
 
-    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
     mDispatcher->notifyKey(&keyArgs);
@@ -4045,8 +4678,8 @@
 
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                        InputEventInjectionSync::NONE));
 
     // Window does not get focus event or key down.
     window->assertNoEvents();
@@ -4203,23 +4836,23 @@
 
         // Window should receive key down event.
         mWindow->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
-                              0 /*expectedFlags*/);
+                              /*expectedFlags=*/0);
     }
 };
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) {
-    sendAndConsumeKeyDown(1 /* deviceId */);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
         expectKeyRepeatOnce(repeatCount);
     }
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeatFromTwoDevices) {
-    sendAndConsumeKeyDown(1 /* deviceId */);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
         expectKeyRepeatOnce(repeatCount);
     }
-    sendAndConsumeKeyDown(2 /* deviceId */);
+    sendAndConsumeKeyDown(/*deviceId=*/2);
     /* repeatCount will start from 1 for deviceId 2 */
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
         expectKeyRepeatOnce(repeatCount);
@@ -4227,42 +4860,42 @@
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterUp) {
-    sendAndConsumeKeyDown(1 /* deviceId */);
-    expectKeyRepeatOnce(1 /*repeatCount*/);
-    sendAndConsumeKeyUp(1 /* deviceId */);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
+    expectKeyRepeatOnce(/*repeatCount=*/1);
+    sendAndConsumeKeyUp(/*deviceId=*/1);
     mWindow->assertNoEvents();
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatAfterStaleDeviceKeyUp) {
-    sendAndConsumeKeyDown(1 /* deviceId */);
-    expectKeyRepeatOnce(1 /*repeatCount*/);
-    sendAndConsumeKeyDown(2 /* deviceId */);
-    expectKeyRepeatOnce(1 /*repeatCount*/);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
+    expectKeyRepeatOnce(/*repeatCount=*/1);
+    sendAndConsumeKeyDown(/*deviceId=*/2);
+    expectKeyRepeatOnce(/*repeatCount=*/1);
     // Stale key up from device 1.
-    sendAndConsumeKeyUp(1 /* deviceId */);
+    sendAndConsumeKeyUp(/*deviceId=*/1);
     // Device 2 is still down, keep repeating
-    expectKeyRepeatOnce(2 /*repeatCount*/);
-    expectKeyRepeatOnce(3 /*repeatCount*/);
+    expectKeyRepeatOnce(/*repeatCount=*/2);
+    expectKeyRepeatOnce(/*repeatCount=*/3);
     // Device 2 key up
-    sendAndConsumeKeyUp(2 /* deviceId */);
+    sendAndConsumeKeyUp(/*deviceId=*/2);
     mWindow->assertNoEvents();
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatStopsAfterRepeatingKeyUp) {
-    sendAndConsumeKeyDown(1 /* deviceId */);
-    expectKeyRepeatOnce(1 /*repeatCount*/);
-    sendAndConsumeKeyDown(2 /* deviceId */);
-    expectKeyRepeatOnce(1 /*repeatCount*/);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
+    expectKeyRepeatOnce(/*repeatCount=*/1);
+    sendAndConsumeKeyDown(/*deviceId=*/2);
+    expectKeyRepeatOnce(/*repeatCount=*/1);
     // Device 2 which holds the key repeating goes up, expect the repeating to stop.
-    sendAndConsumeKeyUp(2 /* deviceId */);
+    sendAndConsumeKeyUp(/*deviceId=*/2);
     // Device 1 still holds key down, but the repeating was already stopped
     mWindow->assertNoEvents();
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInputDevice) {
     sendAndConsumeKeyDown(DEVICE_ID);
-    expectKeyRepeatOnce(1 /*repeatCount*/);
-    NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID);
+    expectKeyRepeatOnce(/*repeatCount=*/1);
+    NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID);
     mDispatcher->notifyDeviceReset(&args);
     mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
                           AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS);
@@ -4270,7 +4903,7 @@
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFromInputDispatcher) {
-    sendAndConsumeKeyDown(1 /* deviceId */);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
         InputEvent* repeatEvent = mWindow->consume();
         ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
@@ -4280,7 +4913,7 @@
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEventId) {
-    sendAndConsumeKeyDown(1 /* deviceId */);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
 
     std::unordered_set<int32_t> idSet;
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
@@ -4626,11 +5259,11 @@
         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, 0 /*repeatCount*/, eventTime, eventTime);
+                         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,
-                  mDispatcher->injectInputEvent(&event, {} /*targetUid*/,
+                  mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
                                                 InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
                                                 policyFlags | additionalPolicyFlags));
 
@@ -4665,7 +5298,7 @@
 
         const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER;
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  mDispatcher->injectInputEvent(&event, {} /*targetUid*/,
+                  mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
                                                 InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
                                                 policyFlags | additionalPolicyFlags));
 
@@ -4685,26 +5318,26 @@
     // Must have POLICY_FLAG_FILTERED here to indicate that the event has gone through the input
     // filter. Without it, the event will no different from a regularly injected event, and the
     // injected device id will be overwritten.
-    testInjectedKey(POLICY_FLAG_FILTERED, 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/,
-                    0 /*flags*/);
+    testInjectedKey(POLICY_FLAG_FILTERED, /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3,
+                    /*flags=*/0);
 }
 
 TEST_F(InputFilterInjectionPolicyTest, KeyEventsInjectedFromAccessibility_HaveAccessibilityFlag) {
     testInjectedKey(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY,
-                    3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/,
+                    /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3,
                     AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
 }
 
 TEST_F(InputFilterInjectionPolicyTest,
        MotionEventsInjectedFromAccessibility_HaveAccessibilityFlag) {
     testInjectedMotion(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY,
-                       3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/,
+                       /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3,
                        AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
 }
 
 TEST_F(InputFilterInjectionPolicyTest, RegularInjectedEvents_ReceiveVirtualDeviceId) {
-    testInjectedKey(0 /*policyFlags*/, 3 /*injectedDeviceId*/,
-                    VIRTUAL_KEYBOARD_ID /*resolvedDeviceId*/, 0 /*flags*/);
+    testInjectedKey(/*policyFlags=*/0, /*injectedDeviceId=*/3,
+                    /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0);
 }
 
 class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest {
@@ -4803,7 +5436,7 @@
     const MotionEvent event =
             MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20))
                     .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event))
@@ -5080,9 +5713,9 @@
     mWindow->consumeFocusEvent(false);
 
     InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/,
-                      false /* allowKeyRepeat */);
+            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms,
+                      /*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.
@@ -5109,8 +5742,8 @@
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 
     mWindow->finishEvent(*sequenceNum);
-    mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
-                          ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+    mWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
 }
@@ -5144,8 +5777,8 @@
     // 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, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */);
+            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
     const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
@@ -5168,12 +5801,12 @@
     // 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, 1 /*repeatCount*/, eventTime, eventTime);
+                     AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime);
 
     const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
 
     InputEventInjectionResult result =
-            mDispatcher->injectInputEvent(&event, {} /* targetUid */,
+            mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
                                           InputEventInjectionSync::WAIT_FOR_RESULT,
                                           INJECT_EVENT_TIMEOUT, policyFlags);
     ASSERT_EQ(InputEventInjectionResult::FAILED, result)
@@ -5195,8 +5828,8 @@
     // 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, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */);
+            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
     const std::chrono::duration appTimeout =
             mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -5219,7 +5852,7 @@
 
     // Once a focused event arrives, we get an ANR for this application
     const InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
                       InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
 
@@ -5277,8 +5910,8 @@
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy);
 
     spy->finishEvent(*sequenceNum);
-    spy->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
-                      0 /*flags*/);
+    spy->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid());
 }
@@ -5400,8 +6033,8 @@
     mFakePolicy->assertNotifyAnrWasNotCalled();
     // When the ANR happened, dispatcher should abort the current event stream via ACTION_CANCEL
     mWindow->consumeMotionDown();
-    mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
-                          ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+    mWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
     mWindow->assertNoEvents();
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
@@ -5435,7 +6068,7 @@
     // we will receive INJECTION_TIMED_OUT as the result.
 
     InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
                       InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
     // Key will not be sent to the window, yet, because the window is still processing events
@@ -5470,8 +6103,8 @@
     // Don't finish the events yet, and send a key
     // Injection is async, so it will succeed
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                        InputEventInjectionSync::NONE));
     // At this point, key is still pending, and should not be sent to the application yet.
     std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
     ASSERT_FALSE(keySequenceNum);
@@ -5553,7 +6186,7 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
     mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -5627,7 +6260,7 @@
 TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
     tapOnFocusedWindow();
     mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // Receive the events, but don't respond
     std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(downEventSequenceNum);
@@ -5716,8 +6349,8 @@
     // window even if motions are still being processed.
 
     InputEventInjectionResult result =
-            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/);
+            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms);
     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
@@ -5756,7 +6389,7 @@
                                ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION});
     mDispatcher->notifyMotion(&motionArgs);
     mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
 
     // Touch Window 2
     motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
@@ -5818,9 +6451,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, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/,
-                      false /* allowKeyRepeat */);
+            injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms,
+                      /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
 
     // Wait until dispatcher starts the "no focused window" timer. If we don't wait here,
@@ -5870,7 +6503,7 @@
         mNoInputWindow =
                 sp<FakeWindowHandle>::make(mApplication, mDispatcher,
                                            "Window without input channel", ADISPLAY_ID_DEFAULT,
-                                           std::make_optional<sp<IBinder>>(nullptr) /*token*/);
+                                           /*token=*/std::make_optional<sp<IBinder>>(nullptr));
         mNoInputWindow->setNoInputChannel(true);
         mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
         // It's perfectly valid for this window to not have an associated input channel
@@ -6060,8 +6693,8 @@
 
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+              injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                        InputEventInjectionSync::NONE));
 
     mMirror->setVisible(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
@@ -6709,7 +7342,7 @@
         // Transfer touch focus to the drag window
         bool transferred =
                 mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(),
-                                                true /* isDragDrop */);
+                                                /*isDragDrop=*/true);
         if (transferred) {
             mWindow->consumeMotionCancel();
             mDragWindow->consumeMotionDown();
@@ -6765,8 +7398,8 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -6909,14 +7542,14 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeMotionPointerDown(1 /* pointerIndex */);
+    mWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
 
     // Should not perform drag and drop when window has multi fingers.
     ASSERT_FALSE(startDrag(false));
@@ -6936,9 +7569,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(
-                            PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -6953,9 +7585,8 @@
     const MotionEvent secondFingerMoveEvent =
             MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(
-                            PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
@@ -6968,9 +7599,8 @@
     const MotionEvent secondFingerUpEvent =
             MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(
-                            PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
@@ -7000,7 +7630,7 @@
                                                          .y(100))
                                         .build()));
     windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN,
-                                    SECOND_DISPLAY_ID, 0 /* expectedFlag */);
+                                    SECOND_DISPLAY_ID, /*expectedFlag=*/0);
     // Update window again.
     mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
 
@@ -7093,7 +7723,7 @@
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
-    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
     NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
@@ -7139,7 +7769,7 @@
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
     setFocusedWindow(window);
-    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
     NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
@@ -7185,7 +7815,7 @@
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
     setFocusedWindow(window);
-    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+    window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
     NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
@@ -7244,8 +7874,7 @@
 
         // Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode.
         if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID,
-                                        WINDOW_UID, true /* hasPermission */,
-                                        ADISPLAY_ID_DEFAULT)) {
+                                        WINDOW_UID, /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)) {
             mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
             mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
             mThirdWindow->assertNoEvents();
@@ -7253,7 +7882,7 @@
 
         // Set secondary display initial touch mode to InputDispatcher::kDefaultInTouchMode.
         if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, SECONDARY_WINDOW_PID,
-                                        SECONDARY_WINDOW_UID, true /* hasPermission */,
+                                        SECONDARY_WINDOW_UID, /*hasPermission=*/true,
                                         SECOND_DISPLAY_ID)) {
             mWindow->assertNoEvents();
             mSecondWindow->assertNoEvents();
@@ -7275,7 +7904,7 @@
     const WindowInfo& windowInfo = *mWindow->getInfo();
     changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode,
                                               windowInfo.ownerPid, windowInfo.ownerUid,
-                                              false /*  hasPermission */);
+                                              /* hasPermission=*/false);
 }
 
 TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTouchMode) {
@@ -7284,7 +7913,7 @@
     int32_t ownerUid = windowInfo.ownerUid;
     mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
     ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid,
-                                             ownerUid, false /*hasPermission*/,
+                                             ownerUid, /*hasPermission=*/false,
                                              ADISPLAY_ID_DEFAULT));
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -7296,14 +7925,14 @@
     int32_t ownerUid = windowInfo.ownerUid;
     mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
     changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, ownerPid,
-                                              ownerUid, true /*hasPermission*/);
+                                              ownerUid, /*hasPermission=*/true);
 }
 
 TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTouchMode) {
     const WindowInfo& windowInfo = *mWindow->getInfo();
     ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode,
                                              windowInfo.ownerPid, windowInfo.ownerUid,
-                                             true /*hasPermission*/, ADISPLAY_ID_DEFAULT));
+                                             /*hasPermission=*/true, ADISPLAY_ID_DEFAULT));
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -7312,7 +7941,7 @@
     const WindowInfo& windowInfo = *mThirdWindow->getInfo();
     ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode,
                                             windowInfo.ownerPid, windowInfo.ownerUid,
-                                            true /*hasPermission*/, SECOND_DISPLAY_ID));
+                                            /*hasPermission=*/true, SECOND_DISPLAY_ID));
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
     mThirdWindow->consumeTouchModeEvent(!InputDispatcher::kDefaultInTouchMode);
@@ -7332,7 +7961,7 @@
     const WindowInfo& windowInfo = *mWindow->getInfo();
     ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode,
                                             windowInfo.ownerPid, windowInfo.ownerUid,
-                                            false /*hasPermission*/, ADISPLAY_ID_DEFAULT));
+                                            /*hasPermission=*/false, ADISPLAY_ID_DEFAULT));
 }
 
 class InputDispatcherSpyWindowTest : public InputDispatcherTest {
@@ -7544,16 +8173,15 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(
-                            PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowRight->consumeMotionDown();
-    spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+    spy->consumeMotionPointerDown(/*pointerIndex=*/1);
 }
 
 /**
@@ -7577,15 +8205,14 @@
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(
-                            PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionPointerDown(1 /*pointerIndex*/);
+    window->consumeMotionPointerDown(/*pointerIndex=*/1);
     spyRight->consumeMotionDown();
 }
 
@@ -7617,10 +8244,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(200))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7628,7 +8253,7 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionPointerDown(1 /* pointerIndex */);
+    spy->consumeMotionPointerDown(/*pointerIndex=*/1);
 }
 
 /**
@@ -7742,33 +8367,29 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(200))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+    spy->consumeMotionPointerDown(/*pointerIndex=*/1);
 
     // 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)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(200))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+            << "Inject motion event should return InputEventInjectionResult::FAILED";
 
     spy->assertNoEvents();
     window->assertNoEvents();
@@ -7797,10 +8418,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(150)
-                                     .y(150))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7814,11 +8433,9 @@
             MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(150)
-                                     .y(150))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7862,8 +8479,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7909,10 +8526,8 @@
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .displayId(ADISPLAY_ID_DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(150)
-                                     .y(150))
+                    .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
@@ -7951,7 +8566,7 @@
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
         mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
         setFocusedWindow(window);
-        window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+        window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
         return {std::move(overlay), std::move(window)};
     }
 
@@ -8075,9 +8690,9 @@
     }
 
     InputEventInjectionResult injectTargetedKey(int32_t action) const {
-        return inputdispatcher::injectKey(mDispatcher, action, 0 /* repeatCount*/, ADISPLAY_ID_NONE,
+        return inputdispatcher::injectKey(mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE,
                                           InputEventInjectionSync::WAIT_FOR_RESULT,
-                                          INJECT_EVENT_TIMEOUT, false /*allowKeyRepeat*/, {mUid},
+                                          INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid},
                                           mPolicyFlags);
     }
 
diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp
index 040c8da..161a24f 100644
--- a/services/inputflinger/tests/InputProcessorConverter_test.cpp
+++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp
@@ -38,15 +38,15 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 2);
     coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5);
     static constexpr nsecs_t downTime = 2;
-    NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/,
-                                3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
-                                4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/,
-                                0 /*flags*/, AMETA_NONE, 0 /*buttonState*/,
+    NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
+                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
+                                /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
                                 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
-                                1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/,
-                                0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0,
+                                /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                 AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
-                                {} /*videoFrames*/);
+                                /*videoFrames=*/{});
     return motionArgs;
 }
 
diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp
index 380001c..b6deed8 100644
--- a/services/inputflinger/tests/InputProcessor_test.cpp
+++ b/services/inputflinger/tests/InputProcessor_test.cpp
@@ -44,15 +44,15 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
     static constexpr nsecs_t downTime = 2;
-    NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/,
-                                3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
-                                4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/,
-                                0 /*flags*/, AMETA_NONE, 0 /*buttonState*/,
+    NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
+                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
+                                /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
                                 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
-                                1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/,
-                                0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0,
+                                /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                 AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
-                                {} /*videoFrames*/);
+                                /*videoFrames=*/{});
     return motionArgs;
 }
 
@@ -70,7 +70,7 @@
  */
 TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) {
     // Create a basic configuration change and send to processor
-    NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/);
+    NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
 
     mProcessor->notifyConfigurationChanged(&args);
     NotifyConfigurationChangedArgs outArgs;
@@ -80,10 +80,10 @@
 
 TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) {
     // Create a basic key event and send to processor
-    NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/,
-                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/,
-                       AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/,
-                       AMETA_NONE, 6 /*downTime*/);
+    NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
+                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+                       AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
+                       AMETA_NONE, /*downTime=*/6);
 
     mProcessor->notifyKey(&args);
     NotifyKeyArgs outArgs;
@@ -108,8 +108,8 @@
  * Expect that the event is received by the next input stage, unmodified.
  */
 TEST_F(InputProcessorTest, SendToNextStage_NotifySwitchArgs) {
-    NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/,
-                          5 /*switchMask*/);
+    NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3,
+                          /*switchValues=*/4, /*switchMask=*/5);
 
     mProcessor->notifySwitch(&args);
     NotifySwitchArgs outArgs;
@@ -122,7 +122,7 @@
  * Expect that the event is received by the next input stage, unmodified.
  */
 TEST_F(InputProcessorTest, SendToNextStage_NotifyDeviceResetArgs) {
-    NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*deviceId*/);
+    NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3);
 
     mProcessor->notifyDeviceReset(&args);
     NotifyDeviceResetArgs outArgs;
@@ -252,7 +252,7 @@
  * Make sure MotionClassifier does not crash when a device is reset.
  */
 TEST_F(MotionClassifierTest, DeviceReset_DoesNotCrash) {
-    NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*deviceId*/);
+    NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3);
     ASSERT_NO_FATAL_FAILURE(mMotionClassifier->reset(args));
 }
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index feda191..fe7af80 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -367,7 +367,7 @@
 
     // Add an internal viewport, then clear it
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    true /*isActive*/, uniqueId, NO_PORT, ViewportType::INTERNAL);
+                                    /*isActive=*/true, uniqueId, NO_PORT, ViewportType::INTERNAL);
 
     // Check matching by uniqueId
     internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
@@ -397,19 +397,19 @@
 
     // Add an internal viewport
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    true /*isActive*/, internalUniqueId, NO_PORT,
+                                    /*isActive=*/true, internalUniqueId, NO_PORT,
                                     ViewportType::INTERNAL);
     // Add an external viewport
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    true /*isActive*/, externalUniqueId, NO_PORT,
+                                    /*isActive=*/true, externalUniqueId, NO_PORT,
                                     ViewportType::EXTERNAL);
     // Add an virtual viewport
     mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, virtualUniqueId1, NO_PORT,
+                                    ui::ROTATION_0, /*isActive=*/true, virtualUniqueId1, NO_PORT,
                                     ViewportType::VIRTUAL);
     // Add another virtual viewport
     mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, virtualUniqueId2, NO_PORT,
+                                    ui::ROTATION_0, /*isActive=*/true, virtualUniqueId2, NO_PORT,
                                     ViewportType::VIRTUAL);
 
     // Check matching by type for internal
@@ -459,10 +459,10 @@
         mFakePolicy->clearViewports();
         // Add a viewport
         mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                        true /*isActive*/, uniqueId1, NO_PORT, type);
+                                        /*isActive=*/true, uniqueId1, NO_PORT, type);
         // Add another viewport
         mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                        true /*isActive*/, uniqueId2, NO_PORT, type);
+                                        /*isActive=*/true, uniqueId2, NO_PORT, type);
 
         // Check that correct display viewport was returned by comparing the display IDs.
         std::optional<DisplayViewport> viewport1 =
@@ -502,10 +502,10 @@
     // Add the default display first and ensure it gets returned.
     mFakePolicy->clearViewports();
     mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
+                                    ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
+                                    ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
                                     ViewportType::INTERNAL);
 
     std::optional<DisplayViewport> viewport =
@@ -517,10 +517,10 @@
     // Add the default display second to make sure order doesn't matter.
     mFakePolicy->clearViewports();
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
+                                    ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
                                     ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
+                                    ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
 
     viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
@@ -545,10 +545,10 @@
     mFakePolicy->clearViewports();
     // Add a viewport that's associated with some display port that's not of interest.
     mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    true /*isActive*/, uniqueId1, hdmi3, type);
+                                    /*isActive=*/true, uniqueId1, hdmi3, type);
     // Add another viewport, connected to HDMI1 port
     mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    true /*isActive*/, uniqueId2, hdmi1, type);
+                                    /*isActive=*/true, uniqueId2, hdmi1, type);
 
     // Check that correct display viewport was returned by comparing the display ports.
     std::optional<DisplayViewport> hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1);
@@ -1002,9 +1002,9 @@
     // Add default and second display.
     mFakePolicy->clearViewports();
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    true /*isActive*/, "local:0", NO_PORT, ViewportType::INTERNAL);
+                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, "local:1", hdmi1,
+                                    ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1,
                                     ViewportType::EXTERNAL);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     mReader->loopOnce();
@@ -1255,15 +1255,15 @@
                          .maxBrightness = 255,
                          .flags = InputLightClass::BRIGHTNESS,
                          .path = ""};
-    mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info));
-    mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55);
+    mFakeEventHub->addRawLightInfo(/*rawId=*/1, std::move(info));
+    mFakeEventHub->fakeLightBrightness(/*rawId=*/1, 0x55);
 
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
 
-    ASSERT_TRUE(controller.setLightColor(1 /* lightId */, LIGHT_BRIGHTNESS));
-    ASSERT_EQ(controller.getLightColor(1 /* lightId */), LIGHT_BRIGHTNESS);
-    ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS));
-    ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS);
+    ASSERT_TRUE(controller.setLightColor(/*lightId=*/1, LIGHT_BRIGHTNESS));
+    ASSERT_EQ(controller.getLightColor(/*lightId=*/1), LIGHT_BRIGHTNESS);
+    ASSERT_TRUE(mReader->setLightColor(deviceId, /*lightId=*/1, LIGHT_BRIGHTNESS));
+    ASSERT_EQ(mReader->getLightColor(deviceId, /*lightId=*/1), LIGHT_BRIGHTNESS);
 }
 
 // --- InputReaderIntegrationTest ---
@@ -1288,8 +1288,8 @@
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
         mFakePointerController = std::make_shared<FakePointerController>();
         mFakePolicy->setPointerController(mFakePointerController);
-        mTestListener = std::make_unique<TestInputListener>(2000ms /*eventHappenedTimeout*/,
-                                                            30ms /*eventDidNotHappenTimeout*/);
+        mTestListener = std::make_unique<TestInputListener>(/*eventHappenedTimeout=*/2000ms,
+                                                            /*eventDidNotHappenTimeout=*/30ms);
 
         mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy,
                                                 *mTestListener);
@@ -1326,7 +1326,7 @@
     // An invalid input device that is only used for this test.
     class InvalidUinputDevice : public UinputDevice {
     public:
-        InvalidUinputDevice() : UinputDevice("Invalid Device", 99 /*productId*/) {}
+        InvalidUinputDevice() : UinputDevice("Invalid Device", /*productId=*/99) {}
 
     private:
         void configureDevice(int fd, uinput_user_dev* device) override {}
@@ -1478,7 +1478,7 @@
                                       ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
                                       ViewportType viewportType) {
-        mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
+        mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true,
                                         uniqueId, physicalPort, viewportType);
         mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     }
@@ -2452,7 +2452,7 @@
 
     // Prepare displays.
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, true /*isActive*/, UNIQUE_ID, hdmi,
+                                    ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi,
                                     ViewportType::INTERNAL);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -2528,8 +2528,8 @@
     constexpr int32_t TEST_EVENTHUB_ID = 10;
     mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY);
 
-    InputDevice device(mReader->getContext(), 1 /*id*/, 2 /*generation*/, {} /*identifier*/);
-    device.addEventHubDevice(TEST_EVENTHUB_ID, true /*populateMappers*/);
+    InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{});
+    device.addEventHubDevice(TEST_EVENTHUB_ID, /*populateMappers=*/true);
     device.removeEventHubDevice(TEST_EVENTHUB_ID);
     std::string dumpStr, eventHubDevStr;
     device.dump(dumpStr, eventHubDevStr);
@@ -2609,12 +2609,12 @@
     VibrationElement pattern(2);
     VibrationSequence sequence(2);
     pattern.duration = std::chrono::milliseconds(200);
-    pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 2},
-                        {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+    pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 2},
+                        {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
     sequence.addElement(pattern);
     pattern.duration = std::chrono::milliseconds(500);
-    pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 4},
-                        {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+    pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 4},
+                        {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
     sequence.addElement(pattern);
 
     std::vector<int64_t> timings = {0, 1};
@@ -2622,7 +2622,7 @@
 
     ASSERT_FALSE(mapper.isVibrating());
     // Start vibrating
-    std::list<NotifyArgs> out = mapper.vibrate(sequence, -1 /* repeat */, VIBRATION_TOKEN);
+    std::list<NotifyArgs> out = mapper.vibrate(sequence, /*repeat=*/-1, VIBRATION_TOKEN);
     ASSERT_TRUE(mapper.isVibrating());
     // Verify vibrator state listener was notified.
     mReader->loopOnce();
@@ -2981,12 +2981,12 @@
     NotifyKeyArgs args;
 
     // Key down
-    process(mapper, ARBITRARY_TIME, 12 /*readTime*/, EV_KEY, KEY_HOME, 1);
+    process(mapper, ARBITRARY_TIME, /*readTime=*/12, EV_KEY, KEY_HOME, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(12, args.readTime);
 
     // Key up
-    process(mapper, ARBITRARY_TIME, 15 /*readTime*/, EV_KEY, KEY_HOME, 1);
+    process(mapper, ARBITRARY_TIME, /*readTime=*/15, EV_KEY, KEY_HOME, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(15, args.readTime);
 }
@@ -3369,7 +3369,7 @@
                                                     AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               0 /*changes*/);
+                               /*changes=*/0);
     unused += device2->reset(ARBITRARY_TIME);
 
     // Prepared displays and associated info.
@@ -3479,7 +3479,7 @@
                                                     AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               0 /*changes*/);
+                               /*changes=*/0);
     unused += device2->reset(ARBITRARY_TIME);
 
     ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL));
@@ -3540,7 +3540,7 @@
                                                     AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               0 /*changes*/);
+                               /*changes=*/0);
     unused += device2->reset(ARBITRARY_TIME);
 
     // Initial metastate is AMETA_NONE.
@@ -4508,8 +4508,8 @@
  */
 TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
     addConfigurationProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(5.f /*scale*/, 0.f /*low threshold*/,
-                                               100.f /*high threshold*/, 10.f /*acceleration*/);
+    const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
+                                               /*high threshold=*/100.f, /*acceleration=*/10.f);
     mFakePolicy->setVelocityControlParams(testParams);
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
@@ -6678,6 +6678,52 @@
     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 = addMapperAndConfigure<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(AMOTION_EVENT_TOOL_TYPE_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 = addMapperAndConfigure<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(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+    ASSERT_FALSE(fakePointerController->isPointerShown());
+}
+
 // --- TouchDisplayProjectionTest ---
 
 class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
@@ -8886,18 +8932,18 @@
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
     prepareDisplay(ui::ROTATION_0);
-    process(mapper, 10, 11 /*readTime*/, EV_ABS, ABS_MT_TRACKING_ID, 1);
-    process(mapper, 15, 16 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 100);
-    process(mapper, 20, 21 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 100);
-    process(mapper, 25, 26 /*readTime*/, EV_SYN, SYN_REPORT, 0);
+    process(mapper, 10, /*readTime=*/11, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    process(mapper, 15, /*readTime=*/16, EV_ABS, ABS_MT_POSITION_X, 100);
+    process(mapper, 20, /*readTime=*/21, EV_ABS, ABS_MT_POSITION_Y, 100);
+    process(mapper, 25, /*readTime=*/26, EV_SYN, SYN_REPORT, 0);
 
     NotifyMotionArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(26, args.readTime);
 
-    process(mapper, 30, 31 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 110);
-    process(mapper, 30, 32 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 220);
-    process(mapper, 30, 33 /*readTime*/, EV_SYN, SYN_REPORT, 0);
+    process(mapper, 30, /*readTime=*/31, EV_ABS, ABS_MT_POSITION_X, 110);
+    process(mapper, 30, /*readTime=*/32, EV_ABS, ABS_MT_POSITION_Y, 220);
+    process(mapper, 30, /*readTime=*/33, EV_SYN, SYN_REPORT, 0);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(33, args.readTime);
@@ -8911,7 +8957,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     // Don't set touch.enableForInactiveViewport to verify the default behavior.
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+                                    /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -8931,7 +8977,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.enableForInactiveViewport", "1");
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+                                    /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -8948,7 +8994,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.enableForInactiveViewport", "0");
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    true /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+                                    /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     std::optional<DisplayViewport> optionalDisplayViewport =
             mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID);
     ASSERT_TRUE(optionalDisplayViewport.has_value());
@@ -9011,14 +9057,14 @@
                       ftl::Flags<InputDeviceClass>(0));
 
     mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX,
-                                   0 /*flat*/, 0 /*fuzz*/);
+                                   /*flat=*/0, /*fuzz=*/0);
     mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX,
-                                   0 /*flat*/, 0 /*fuzz*/);
+                                   /*flat=*/0, /*fuzz=*/0);
     mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_TRACKING_ID, RAW_ID_MIN, RAW_ID_MAX,
-                                   0 /*flat*/, 0 /*fuzz*/);
+                                   /*flat=*/0, /*fuzz=*/0);
     mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_SLOT, RAW_SLOT_MIN, RAW_SLOT_MAX,
-                                   0 /*flat*/, 0 /*fuzz*/);
-    mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, 0 /*value*/);
+                                   /*flat=*/0, /*fuzz=*/0);
+    mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, /*value=*/0);
     mFakeEventHub->addConfigurationProperty(SECOND_EVENTHUB_ID, String8("touch.deviceType"),
                                             String8("touchScreen"));
 
@@ -9026,7 +9072,7 @@
     MultiTouchInputMapper& mapper2 = device2->addMapper<MultiTouchInputMapper>(SECOND_EVENTHUB_ID);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               0 /*changes*/);
+                               /*changes=*/0);
     unused += device2->reset(ARBITRARY_TIME);
 
     // Setup PointerController.
@@ -9757,6 +9803,58 @@
                   WithToolType(AMOTION_EVENT_TOOL_TYPE_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 = addMapperAndConfigure<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(AMOTION_EVENT_TOOL_TYPE_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 = addMapperAndConfigure<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(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+    ASSERT_FALSE(fakePointerController->isPointerShown());
+}
+
 // --- MultiTouchInputMapperTest_ExternalDevice ---
 
 class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
@@ -9986,6 +10084,26 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 100 * scale, 100 * scale, 0,
                                                 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_DOWN, 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_UP, args.action);
 }
 
 TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) {
@@ -10108,7 +10226,7 @@
     // The min freeform gesture width is 25units/mm x 30mm = 750
     // which is greater than fraction of the diagnal length of the touchpad (349).
     // Thus, MaxSwipWidth is 750.
-    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
     NotifyMotionArgs motionArgs;
 
@@ -10168,7 +10286,7 @@
     // The min freeform gesture width is 5units/mm x 30mm = 150
     // which is greater than fraction of the diagnal length of the touchpad (349).
     // Thus, MaxSwipWidth is the fraction of the diagnal length, 349.
-    preparePointerMode(5 /*xResolution*/, 5 /*yResolution*/);
+    preparePointerMode(/*xResolution=*/5, /*yResolution=*/5);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
     NotifyMotionArgs motionArgs;
 
@@ -10224,7 +10342,7 @@
  * freeform gestures after two fingers start to move downwards.
  */
 TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) {
-    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
@@ -10319,7 +10437,7 @@
 }
 
 TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) {
-    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
     NotifyMotionArgs motionArgs;
 
@@ -10365,7 +10483,7 @@
 }
 
 TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
-    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index 1f4b248..fa149db 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -36,10 +36,10 @@
 
 InputEventTimeline getTestTimeline() {
     InputEventTimeline t(
-            /*isDown*/ true,
-            /*eventTime*/ 2,
-            /*readTime*/ 3);
-    ConnectionTimeline expectedCT(/*deliveryTime*/ 6, /* consumeTime*/ 7, /*finishTime*/ 8);
+            /*isDown=*/true,
+            /*eventTime=*/2,
+            /*readTime=*/3);
+    ConnectionTimeline expectedCT(/*deliveryTime=*/6, /*consumeTime=*/7, /*finishTime=*/8);
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
     graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 10;
@@ -87,7 +87,8 @@
 void LatencyTrackerTest::triggerEventReporting(nsecs_t lastEventTime) {
     const nsecs_t triggerEventTime =
             lastEventTime + std::chrono::nanoseconds(ANR_TIMEOUT).count() + 1;
-    mTracker->trackListener(1 /*inputEventId*/, true /*isDown*/, triggerEventTime, 3 /*readTime*/);
+    mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/true, triggerEventTime,
+                            /*readTime=*/3);
 }
 
 void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) {
@@ -136,8 +137,9 @@
  * any additional ConnectionTimeline's.
  */
 TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) {
-    mTracker->trackListener(1 /*inputEventId*/, false /*isDown*/, 2 /*eventTime*/, 3 /*readTime*/);
-    triggerEventReporting(2 /*eventTime*/);
+    mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/false, /*eventTime=*/2,
+                            /*readTime=*/3);
+    triggerEventReporting(/*eventTime=*/2);
     assertReceivedTimeline(InputEventTimeline{false, 2, 3});
 }
 
@@ -145,9 +147,9 @@
  * A single call to trackFinishedEvent should not cause a timeline to be reported.
  */
 TEST_F(LatencyTrackerTest, TrackFinishedEvent_DoesNotTriggerReporting) {
-    mTracker->trackFinishedEvent(1 /*inputEventId*/, connection1, 2 /*deliveryTime*/,
-                                 3 /*consumeTime*/, 4 /*finishTime*/);
-    triggerEventReporting(4 /*eventTime*/);
+    mTracker->trackFinishedEvent(/*inputEventId=*/1, connection1, /*deliveryTime=*/2,
+                                 /*consumeTime=*/3, /*finishTime=*/4);
+    triggerEventReporting(/*eventTime=*/4);
     assertReceivedTimelines({});
 }
 
@@ -158,8 +160,8 @@
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
     graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
-    mTracker->trackGraphicsLatency(1 /*inputEventId*/, connection2, graphicsTimeline);
-    triggerEventReporting(3 /*eventTime*/);
+    mTracker->trackGraphicsLatency(/*inputEventId=*/1, connection2, graphicsTimeline);
+    triggerEventReporting(/*eventTime=*/3);
     assertReceivedTimelines({});
 }
 
@@ -189,10 +191,10 @@
 
     // In the following 2 calls to trackListener, the inputEventId's are the same, but event times
     // are different.
-    mTracker->trackListener(inputEventId, isDown, 1 /*eventTime*/, readTime);
-    mTracker->trackListener(inputEventId, isDown, 2 /*eventTime*/, readTime);
+    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/1, readTime);
+    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime);
 
-    triggerEventReporting(2 /*eventTime*/);
+    triggerEventReporting(/*eventTime=*/2);
     // Since we sent duplicate input events, the tracker should just delete all of them, because it
     // does not have enough information to properly track them.
     assertReceivedTimelines({});
@@ -215,13 +217,13 @@
 
     constexpr int32_t inputEventId2 = 10;
     InputEventTimeline timeline2(
-            /*isDown*/ false,
-            /*eventTime*/ 20,
-            /*readTime*/ 30);
+            /*isDown=*/false,
+            /*eventTime=*/20,
+            /*readTime=*/30);
     timeline2.connectionTimelines.emplace(connection2,
-                                          ConnectionTimeline(/*deliveryTime*/ 60,
-                                                             /*consumeTime*/ 70,
-                                                             /*finishTime*/ 80));
+                                          ConnectionTimeline(/*deliveryTime=*/60,
+                                                             /*consumeTime=*/70,
+                                                             /*finishTime=*/80));
     ConnectionTimeline& connectionTimeline2 = timeline2.connectionTimelines.begin()->second;
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline2;
     graphicsTimeline2[GraphicsTimeline::GPU_COMPLETED_TIME] = 90;
@@ -258,15 +260,15 @@
     const sp<IBinder>& token = timeline.connectionTimelines.begin()->first;
 
     for (size_t i = 1; i <= 100; i++) {
-        mTracker->trackListener(i /*inputEventId*/, timeline.isDown, timeline.eventTime,
+        mTracker->trackListener(/*inputEventId=*/i, timeline.isDown, timeline.eventTime,
                                 timeline.readTime);
         expectedTimelines.push_back(
                 InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime});
     }
     // Now, complete the first event that was sent.
-    mTracker->trackFinishedEvent(1 /*inputEventId*/, token, expectedCT.deliveryTime,
+    mTracker->trackFinishedEvent(/*inputEventId=*/1, token, expectedCT.deliveryTime,
                                  expectedCT.consumeTime, expectedCT.finishTime);
-    mTracker->trackGraphicsLatency(1 /*inputEventId*/, token, expectedCT.graphicsTimeline);
+    mTracker->trackGraphicsLatency(/*inputEventId=*/1, token, expectedCT.graphicsTimeline);
 
     expectedTimelines[0].connectionTimelines.emplace(token, std::move(expectedCT));
     triggerEventReporting(timeline.eventTime);
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index 7265362..9014dfb 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -64,13 +64,13 @@
     }
 
     // Define a valid motion event.
-    NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 0 /*displayId*/,
+    NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, /*displayId=*/0,
                           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,
+                          /*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,
-                          AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {});
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /*videoFrames=*/{});
 
     return args;
 }
@@ -109,26 +109,26 @@
 TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) {
     NotifyMotionArgs args;
 
-    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN);
     assertNotBlocked(args);
 }
 
 TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) {
     NotifyMotionArgs args;
 
-    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, STYLUS);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, STYLUS);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, STYLUS);
     assertNotBlocked(args);
 }
 
@@ -139,24 +139,24 @@
 TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) {
     NotifyMotionArgs args;
 
-    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS);
     NotifyMotionArgs cancelArgs =
-            generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, CANCEL, {{1, 3}}, TOUCHSCREEN);
     cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
     assertResponse(args, {cancelArgs, args});
 
     // Both stylus and touch events continue. Stylus should be not blocked, and touch should be
     // blocked
-    args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{10, 31}}, STYLUS);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN);
     assertDropped(args);
 }
 
@@ -166,17 +166,17 @@
 TEST_F(PreferStylusOverTouchTest, StylusDownAfterTouch) {
     NotifyMotionArgs args;
 
-    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
     // Stylus goes down
-    args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS);
     assertNotBlocked(args);
 }
 
@@ -189,21 +189,21 @@
     constexpr nsecs_t stylusDownTime = 0;
     constexpr nsecs_t touchDownTime = 1;
 
-    args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertDropped(args);
 
     // Stylus should continue to work
-    args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+    args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS);
     assertNotBlocked(args);
 
     // Touch should continue to be blocked
-    args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(touchDownTime, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN);
     assertDropped(args);
 
-    args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN);
     assertDropped(args);
 }
 
@@ -217,23 +217,23 @@
     constexpr nsecs_t touchDownTime = 4;
 
     // Stylus goes down and up
-    args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+    args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
+    args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS);
+    args = generateMotionArgs(stylusDownTime, /*eventTime=*/3, UP, {{10, 31}}, STYLUS);
     assertNotBlocked(args);
 
     // New touch goes down. It should not be blocked
     args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(touchDownTime, /*eventTime=*/5, MOVE, {{1, 3}}, TOUCHSCREEN);
     assertNotBlocked(args);
 
-    args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(touchDownTime, /*eventTime=*/6, UP, {{1, 3}}, TOUCHSCREEN);
     assertNotBlocked(args);
 }
 
@@ -246,25 +246,25 @@
     constexpr nsecs_t stylusDownTime = 0;
     constexpr nsecs_t touchDownTime = 1;
 
-    assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
+    assertNotBlocked(generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS));
 
-    args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertDropped(args);
 
     // Lift the stylus
-    args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+    args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, UP, {{10, 30}}, STYLUS);
     assertNotBlocked(args);
 
     // Touch should continue to be blocked
-    args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(touchDownTime, /*eventTime=*/3, MOVE, {{1, 3}}, TOUCHSCREEN);
     assertDropped(args);
 
-    args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
+    args = generateMotionArgs(touchDownTime, /*eventTime=*/4, UP, {{1, 3}}, TOUCHSCREEN);
     assertDropped(args);
 
     // New touch should go through, though.
     constexpr nsecs_t newTouchDownTime = 5;
-    args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN);
+    args = generateMotionArgs(newTouchDownTime, /*eventTime=*/5, DOWN, {{10, 20}}, TOUCHSCREEN);
     assertNotBlocked(args);
 }
 
@@ -276,20 +276,20 @@
     NotifyMotionArgs args;
 
     // Event from a stylus device, but with finger tool type
-    args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, STYLUS);
     // Keep source stylus, but make the tool type touch
     args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
     assertNotBlocked(args);
 
     // Second pointer (stylus pointer) goes down, from the same device
-    args = generateMotionArgs(1 /*downTime*/, 2 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {10, 20}},
+    args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/2, POINTER_1_DOWN, {{1, 2}, {10, 20}},
                               STYLUS);
     // Keep source stylus, but make the tool type touch
     args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     assertNotBlocked(args);
 
     // Second pointer (stylus pointer) goes down, from the same device
-    args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, MOVE, {{2, 3}, {11, 21}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, MOVE, {{2, 3}, {11, 21}}, STYLUS);
     // Keep source stylus, but make the tool type touch
     args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
     assertNotBlocked(args);
@@ -300,16 +300,16 @@
  */
 TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) {
     NotifyMotionArgs touch1Down =
-            generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertNotBlocked(touch1Down);
 
     NotifyMotionArgs touch2Down =
-            generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{3, 4}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{3, 4}}, TOUCHSCREEN);
     touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
     assertNotBlocked(touch2Down);
 
     NotifyMotionArgs stylusDown =
-            generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+            generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS);
     NotifyMotionArgs cancelArgs1 = touch1Down;
     cancelArgs1.action = CANCEL;
     cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
@@ -328,12 +328,12 @@
 TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) {
     // First device touches down
     NotifyMotionArgs touch1Down =
-            generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertNotBlocked(touch1Down);
 
     // Stylus goes down - touch should be canceled
     NotifyMotionArgs stylusDown =
-            generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
+            generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 30}}, STYLUS);
     NotifyMotionArgs cancelArgs1 = touch1Down;
     cancelArgs1.action = CANCEL;
     cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
@@ -341,44 +341,44 @@
 
     // Stylus goes up
     NotifyMotionArgs stylusUp =
-            generateMotionArgs(2 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+            generateMotionArgs(/*downTime=*/2, /*eventTime=*/3, UP, {{10, 30}}, STYLUS);
     assertNotBlocked(stylusUp);
 
     // Touch from the first device remains blocked
     NotifyMotionArgs touch1Move =
-            generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, MOVE, {{2, 3}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, MOVE, {{2, 3}}, TOUCHSCREEN);
     assertDropped(touch1Move);
 
     // Second touch goes down. It should not be blocked because stylus has already lifted.
     NotifyMotionArgs touch2Down =
-            generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{31, 32}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{31, 32}}, TOUCHSCREEN);
     touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
     assertNotBlocked(touch2Down);
 
     // First device is lifted up. It's already been canceled, so the UP event should be dropped.
     NotifyMotionArgs touch1Up =
-            generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{2, 3}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{2, 3}}, TOUCHSCREEN);
     assertDropped(touch1Up);
 
     // Touch from second device touch should continue to work
     NotifyMotionArgs touch2Move =
-            generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, MOVE, {{32, 33}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, MOVE, {{32, 33}}, TOUCHSCREEN);
     touch2Move.deviceId = SECOND_TOUCH_DEVICE_ID;
     assertNotBlocked(touch2Move);
 
     // Second touch lifts up
     NotifyMotionArgs touch2Up =
-            generateMotionArgs(5 /*downTime*/, 8 /*eventTime*/, UP, {{32, 33}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/5, /*eventTime=*/8, UP, {{32, 33}}, TOUCHSCREEN);
     touch2Up.deviceId = SECOND_TOUCH_DEVICE_ID;
     assertNotBlocked(touch2Up);
 
     // Now that all touch has been lifted, new touch from either first or second device should work
     NotifyMotionArgs touch3Down =
-            generateMotionArgs(9 /*downTime*/, 9 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/9, /*eventTime=*/9, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertNotBlocked(touch3Down);
 
     NotifyMotionArgs touch4Down =
-            generateMotionArgs(10 /*downTime*/, 10 /*eventTime*/, DOWN, {{100, 200}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/10, /*eventTime=*/10, DOWN, {{100, 200}}, TOUCHSCREEN);
     touch4Down.deviceId = SECOND_TOUCH_DEVICE_ID;
     assertNotBlocked(touch4Down);
 }
@@ -403,27 +403,27 @@
 TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) {
     // Touch from device 1 goes down
     NotifyMotionArgs touchDown =
-            generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+            generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN);
     touchDown.source = STYLUS;
     assertNotBlocked(touchDown);
 
     // Stylus from device 2 goes down. Touch should be canceled.
     NotifyMotionArgs args =
-            generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 20}}, STYLUS);
+            generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 20}}, STYLUS);
     NotifyMotionArgs cancelTouchArgs = touchDown;
     cancelTouchArgs.action = CANCEL;
     cancelTouchArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
     assertResponse(args, {cancelTouchArgs, args});
 
     // Introduce a stylus pointer into the device 1 stream. It should be ignored.
-    args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {3, 4}},
+    args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, POINTER_1_DOWN, {{1, 2}, {3, 4}},
                               TOUCHSCREEN);
     args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     args.source = STYLUS;
     assertDropped(args);
 
     // Lift up touch from the mixed touch/stylus device
-    args = generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, CANCEL, {{1, 2}, {3, 4}},
+    args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, CANCEL, {{1, 2}, {3, 4}},
                               TOUCHSCREEN);
     args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     args.source = STYLUS;
@@ -431,19 +431,19 @@
 
     // Stylus from device 2 is still down. Since the device 1 is now identified as a mixed
     // touch/stylus device, its events should go through, even if they are touch.
-    args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{21, 22}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{21, 22}}, TOUCHSCREEN);
     touchDown.source = STYLUS;
     assertResponse(args, {args});
 
     // Reconfigure such that only the stylus device remains
     InputDeviceInfo stylusDevice;
-    stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/,
-                            {} /*identifier*/, "stylus device", false /*external*/,
-                            false /*hasMic*/, ADISPLAY_ID_NONE);
+    stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1,
+                            /*identifier=*/{}, "stylus device", /*external=*/false,
+                            /*hasMic=*/false, ADISPLAY_ID_NONE);
     notifyInputDevicesChanged({stylusDevice});
     // The touchscreen device was removed, so we no longer remember anything about it. We should
     // again start blocking touch events from it.
-    args = generateMotionArgs(6 /*downTime*/, 6 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/6, /*eventTime=*/6, DOWN, {{1, 2}}, TOUCHSCREEN);
     args.source = STYLUS;
     assertDropped(args);
 }
@@ -456,41 +456,41 @@
     NotifyMotionArgs args;
 
     // First stylus is down
-    assertNotBlocked(generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
+    assertNotBlocked(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS));
 
     // Second stylus is down
-    args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{20, 40}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{20, 40}}, STYLUS);
     args.deviceId = SECOND_STYLUS_DEVICE_ID;
     assertNotBlocked(args);
 
     // Touch goes down. It should be ignored.
-    args = generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{1, 2}}, TOUCHSCREEN);
     assertDropped(args);
 
     // Lift the first stylus
-    args = generateMotionArgs(0 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/3, UP, {{10, 30}}, STYLUS);
     assertNotBlocked(args);
 
     // Touch should continue to be blocked
-    args = generateMotionArgs(2 /*downTime*/, 4 /*eventTime*/, UP, {{1, 2}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/4, UP, {{1, 2}}, TOUCHSCREEN);
     assertDropped(args);
 
     // New touch should be blocked because second stylus is still down
-    args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{5, 6}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{5, 6}}, TOUCHSCREEN);
     assertDropped(args);
 
     // Second stylus goes up
-    args = generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{20, 40}}, STYLUS);
+    args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{20, 40}}, STYLUS);
     args.deviceId = SECOND_STYLUS_DEVICE_ID;
     assertNotBlocked(args);
 
     // Current touch gesture should continue to be blocked
     // Touch should continue to be blocked
-    args = generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, UP, {{5, 6}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, UP, {{5, 6}}, TOUCHSCREEN);
     assertDropped(args);
 
     // Now that all styli were lifted, new touch should go through
-    args = generateMotionArgs(8 /*downTime*/, 8 /*eventTime*/, DOWN, {{7, 8}}, TOUCHSCREEN);
+    args = generateMotionArgs(/*downTime=*/8, /*eventTime=*/8, DOWN, {{7, 8}}, TOUCHSCREEN);
     assertNotBlocked(args);
 }
 
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index e12f88e..3f749b1 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -88,8 +88,8 @@
     }
 
     // Define a valid motion event.
-    NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, DEVICE_ID,
-                          AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, POLICY_FLAG_PASS_TO_USER,
+    NotifyMotionArgs args(/* id */ 0, eventTime, /*readTime=*/0, DEVICE_ID,
+                          AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, POLICY_FLAG_PASS_TO_USER,
                           action, /* actionButton */ 0,
                           /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
                           MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
@@ -409,7 +409,7 @@
 
     void SetUp() override {
         mBlocker = std::make_unique<UnwantedInteractionBlocker>(mTestListener,
-                                                                /*enablePalmRejection*/ true);
+                                                                /*enablePalmRejection=*/true);
     }
 };
 
@@ -419,7 +419,7 @@
  */
 TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListener) {
     // Create a basic configuration change and send to blocker
-    NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/);
+    NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
 
     mBlocker->notifyConfigurationChanged(&args);
     NotifyConfigurationChangedArgs outArgs;
@@ -433,10 +433,10 @@
  */
 TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) {
     // Create a basic key event and send to blocker
-    NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/,
-                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/,
-                       AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/,
-                       AMETA_NONE, 6 /*downTime*/);
+    NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
+                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+                       AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
+                       AMETA_NONE, /*downTime=*/6);
 
     mBlocker->notifyKey(&args);
     NotifyKeyArgs outArgs;
@@ -451,7 +451,7 @@
  */
 TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) {
     NotifyMotionArgs motionArgs =
-            generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+            generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
     mBlocker->notifyMotion(&motionArgs);
     NotifyMotionArgs args;
     ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args));
@@ -463,8 +463,8 @@
  * Expect that the event is received by the next input stage, unmodified.
  */
 TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) {
-    NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/,
-                          5 /*switchMask*/);
+    NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3,
+                          /*switchValues=*/4, /*switchMask=*/5);
 
     mBlocker->notifySwitch(&args);
     NotifySwitchArgs outArgs;
@@ -477,7 +477,7 @@
  * Expect that the event is received by the next input stage, unmodified.
  */
 TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) {
-    NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, DEVICE_ID);
+    NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, DEVICE_ID);
 
     mBlocker->notifyDeviceReset(&args);
     NotifyDeviceResetArgs outArgs;
@@ -494,19 +494,19 @@
     NotifyMotionArgs args;
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
-    NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
+    NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID);
     mBlocker->notifyDeviceReset(&resetArgs);
     // Start a new gesture with a DOWN event, even though the previous event stream was incomplete.
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}})));
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}})));
 }
 
 TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) {
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}});
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}});
     args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
     args.source = AINPUT_SOURCE_STYLUS;
     mBlocker->notifyMotion(&args);
@@ -520,9 +520,9 @@
     NotifyMotionArgs args;
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
 
     // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice.
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
@@ -530,7 +530,7 @@
     // The MOVE event continues the gesture that started before 'devices changed', so it should not
     // cause a crash.
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}})));
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}})));
 }
 
 /**
@@ -539,23 +539,23 @@
 TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) {
     NotifyMotionArgs args;
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
     mBlocker->notifyMotion(&args);
-    args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}});
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}});
     mBlocker->notifyMotion(&args);
-    args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}});
+    args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}});
     mBlocker->notifyMotion(&args);
 
     // Now touch down stylus
-    args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 20, 30}});
+    args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 20, 30}});
     args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     args.source |= AINPUT_SOURCE_STYLUS;
     mBlocker->notifyMotion(&args);
-    args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{40, 50, 60}});
+    args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{40, 50, 60}});
     args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     args.source |= AINPUT_SOURCE_STYLUS;
     mBlocker->notifyMotion(&args);
-    args = generateMotionArgs(3 /*downTime*/, 5 /*eventTime*/, UP, {{40, 50, 60}});
+    args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/5, UP, {{40, 50, 60}});
     args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     args.source |= AINPUT_SOURCE_STYLUS;
     mBlocker->notifyMotion(&args);
@@ -569,15 +569,15 @@
  */
 TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) {
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
-    NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
     mBlocker->notifyMotion(&args1);
     std::thread dumpThread([this]() {
         std::string dump;
         mBlocker->dump(dump);
     });
-    NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}});
+    NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}});
     mBlocker->notifyMotion(&args2);
-    NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}});
+    NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}});
     mBlocker->notifyMotion(&args3);
     dumpThread.join();
 }
@@ -589,19 +589,19 @@
 TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) {
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
     // Small touch down
-    NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
     mBlocker->notifyMotion(&args1);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
 
     // Large touch oval on the next move
     NotifyMotionArgs args2 =
-            generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
+            generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
     mBlocker->notifyMotion(&args2);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
 
     // Lift up the touch to force the model to decide on whether it's a palm
     NotifyMotionArgs args3 =
-            generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+            generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
     mBlocker->notifyMotion(&args3);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(CANCEL));
 }
@@ -616,14 +616,14 @@
     InputDeviceInfo info = generateTestDeviceInfo();
     info.addSource(AINPUT_SOURCE_STYLUS);
     mBlocker->notifyInputDevicesChanged({info});
-    NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
     args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args1);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
 
     // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions
     NotifyMotionArgs args2 =
-            generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
+            generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
     args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args2);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
@@ -631,7 +631,7 @@
     // Lift up the stylus. If it were a touch event, this would force the model to decide on whether
     // it's a palm.
     NotifyMotionArgs args3 =
-            generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+            generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
     args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args3);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP));
@@ -648,26 +648,26 @@
     mBlocker->notifyInputDevicesChanged({info});
 
     // Touch down
-    NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
     mBlocker->notifyMotion(&args1);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN));
 
     // Stylus pointer down
-    NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, POINTER_1_DOWN,
+    NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, POINTER_1_DOWN,
                                                 {{1, 2, 3}, {10, 20, 30}});
     args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args2);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN));
 
     // Large touch oval on the next finger move
-    NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, MOVE,
+    NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, MOVE,
                                                 {{1, 2, 300}, {11, 21, 30}});
     args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args3);
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE));
 
     // Lift up the finger pointer. It should be canceled due to the heuristic filter.
-    NotifyMotionArgs args4 = generateMotionArgs(0 /*downTime*/, 3 * RESAMPLE_PERIOD, POINTER_0_UP,
+    NotifyMotionArgs args4 = generateMotionArgs(/*downTime=*/0, 3 * RESAMPLE_PERIOD, POINTER_0_UP,
                                                 {{1, 2, 300}, {11, 21, 30}});
     args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args4);
@@ -675,7 +675,7 @@
             AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED)));
 
     NotifyMotionArgs args5 =
-            generateMotionArgs(0 /*downTime*/, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}});
+            generateMotionArgs(/*downTime=*/0, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}});
     args5.pointerProperties[0].id = args4.pointerProperties[1].id;
     args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args5);
@@ -683,7 +683,7 @@
 
     // Lift up the stylus pointer
     NotifyMotionArgs args6 =
-            generateMotionArgs(0 /*downTime*/, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+            generateMotionArgs(/*downTime=*/0, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
     args6.pointerProperties[0].id = args4.pointerProperties[1].id;
     args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     mBlocker->notifyMotion(&args6);
@@ -701,15 +701,15 @@
     NotifyMotionArgs args;
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})));
     mBlocker->notifyMotion(
-            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
-    NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+            &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})));
+    NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID);
     mBlocker->notifyDeviceReset(&resetArgs);
     // Sending MOVE without a DOWN -> should crash!
     ASSERT_DEATH(
             {
-                mBlocker->notifyMotion(&(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/,
+                mBlocker->notifyMotion(&(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4,
                                                                    MOVE, {{7, 8, 9}})));
             },
             "Could not find slot");
@@ -720,7 +720,7 @@
  */
 TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) {
     ScopedSilentDeath _silentDeath;
-    NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 2, 3}});
+    NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}});
     mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
     ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot");
 }
@@ -741,7 +741,7 @@
     ScopedSilentDeath _silentDeath;
     constexpr nsecs_t downTime = 0;
     NotifyMotionArgs args =
-            generateMotionArgs(downTime, 2 /*eventTime*/, MOVE, {{1406.0, 650.0, 52.0}});
+            generateMotionArgs(downTime, /*eventTime=*/2, MOVE, {{1406.0, 650.0, 52.0}});
     ASSERT_DEATH({ mPalmRejector->processMotion(args); }, "Could not find slot");
 }
 
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index c407cff..2909129 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -37,22 +37,22 @@
 
     const nsecs_t downTime = 2;
     const nsecs_t readTime = downTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
-    NotifyMotionArgs motionArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
-                                downTime /*eventTime*/, readTime,
-                                fdp.ConsumeIntegral<int32_t>() /*deviceId*/, AINPUT_SOURCE_ANY,
+    NotifyMotionArgs motionArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+                                /*eventTime=*/downTime, readTime,
+                                /*deviceId=*/fdp.ConsumeIntegral<int32_t>(), AINPUT_SOURCE_ANY,
                                 ADISPLAY_ID_DEFAULT,
-                                fdp.ConsumeIntegral<uint32_t>() /*policyFlags*/,
+                                /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
                                 AMOTION_EVENT_ACTION_DOWN,
-                                fdp.ConsumeIntegral<int32_t>() /*actionButton*/,
-                                fdp.ConsumeIntegral<int32_t>() /*flags*/, AMETA_NONE,
-                                fdp.ConsumeIntegral<int32_t>() /*buttonState*/,
+                                /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
+                                /*flags=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
+                                /*buttonState=*/fdp.ConsumeIntegral<int32_t>(),
                                 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
-                                1 /*pointerCount*/, &properties, &coords,
-                                fdp.ConsumeFloatingPoint<float>() /*xPrecision*/,
-                                fdp.ConsumeFloatingPoint<float>() /*yPrecision*/,
+                                /*pointerCount=*/1, &properties, &coords,
+                                /*xPrecision=*/fdp.ConsumeFloatingPoint<float>(),
+                                /*yPrecision=*/fdp.ConsumeFloatingPoint<float>(),
                                 AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                 AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
-                                {} /*videoFrames*/);
+                                /*videoFrames=*/{});
     return motionArgs;
 }
 
@@ -68,8 +68,8 @@
                 [&]() -> void {
                     // SendToNextStage_NotifyConfigurationChangedArgs
                     NotifyConfigurationChangedArgs
-                            args(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
-                                 fdp.ConsumeIntegral<nsecs_t>() /*eventTime*/);
+                            args(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+                                 /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>());
                     mClassifier->notifyConfigurationChanged(&args);
                 },
                 [&]() -> void {
@@ -77,15 +77,15 @@
                     const nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
                     const nsecs_t readTime =
                             eventTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
-                    NotifyKeyArgs keyArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
+                    NotifyKeyArgs keyArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
                                           eventTime, readTime,
-                                          fdp.ConsumeIntegral<int32_t>() /*deviceId*/,
+                                          /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
                                           AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT,
-                                          fdp.ConsumeIntegral<uint32_t>() /*policyFlags*/,
+                                          /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
                                           AKEY_EVENT_ACTION_DOWN,
-                                          fdp.ConsumeIntegral<int32_t>() /*flags*/, AKEYCODE_HOME,
-                                          fdp.ConsumeIntegral<int32_t>() /*scanCode*/, AMETA_NONE,
-                                          fdp.ConsumeIntegral<nsecs_t>() /*downTime*/);
+                                          /*flags=*/fdp.ConsumeIntegral<int32_t>(), AKEYCODE_HOME,
+                                          /*scanCode=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
+                                          /*downTime=*/fdp.ConsumeIntegral<nsecs_t>());
 
                     mClassifier->notifyKey(&keyArgs);
                 },
@@ -96,19 +96,20 @@
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifySwitchArgs
-                    NotifySwitchArgs switchArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
-                                                fdp.ConsumeIntegral<nsecs_t>() /*eventTime*/,
-                                                fdp.ConsumeIntegral<uint32_t>() /*policyFlags*/,
-                                                fdp.ConsumeIntegral<uint32_t>() /*switchValues*/,
-                                                fdp.ConsumeIntegral<uint32_t>() /*switchMask*/);
+                    NotifySwitchArgs switchArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+                                                /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+                                                /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
+                                                /*switchValues=*/fdp.ConsumeIntegral<uint32_t>(),
+                                                /*switchMask=*/fdp.ConsumeIntegral<uint32_t>());
 
                     mClassifier->notifySwitch(&switchArgs);
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyDeviceResetArgs
-                    NotifyDeviceResetArgs resetArgs(fdp.ConsumeIntegral<uint32_t>() /*sequenceNum*/,
-                                                    fdp.ConsumeIntegral<nsecs_t>() /*eventTime*/,
-                                                    fdp.ConsumeIntegral<int32_t>() /*deviceId*/);
+                    NotifyDeviceResetArgs resetArgs(
+                            /*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
+                            /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+                            /*deviceId=*/fdp.ConsumeIntegral<int32_t>());
 
                     mClassifier->notifyDeviceReset(&resetArgs);
                 },
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index a80839c..20242b1 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -182,13 +182,13 @@
     VibrationSequence pattern(patternCount);
     for (size_t i = 0; i < patternCount; ++i) {
         VibrationElement element(i);
-        element.addChannel(fdp->ConsumeIntegral<int32_t>() /* vibratorId */,
-                           fdp->ConsumeIntegral<uint8_t>() /* amplitude */);
+        element.addChannel(/*vibratorId=*/fdp->ConsumeIntegral<int32_t>(),
+                           /*amplitude=*/fdp->ConsumeIntegral<uint8_t>());
         pattern.addElement(element);
     }
     reader->vibrate(fdp->ConsumeIntegral<int32_t>(), pattern,
-                    fdp->ConsumeIntegral<ssize_t>() /*repeat*/,
-                    fdp->ConsumeIntegral<int32_t>() /*token*/);
+                    /*repeat=*/fdp->ConsumeIntegral<ssize_t>(),
+                    /*token=*/fdp->ConsumeIntegral<int32_t>());
     reader->start();
 
     // Loop through mapper operations until randomness is exhausted.
diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp
index 398cdf9..e9c8335 100644
--- a/services/sensorservice/SensorInterface.cpp
+++ b/services/sensorservice/SensorInterface.cpp
@@ -87,14 +87,15 @@
 
 // ---------------------------------------------------------------------------
 
-RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback)
+RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp<SensorCallback> callback)
   : BaseSensor(sensor), mCallback(std::move(callback)) {
 }
 
 status_t RuntimeSensor::activate(void*, bool enabled) {
     if (enabled != mEnabled) {
         mEnabled = enabled;
-        mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+        return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, mSamplingPeriodNs,
+                mBatchReportLatencyNs);
     }
     return OK;
 }
@@ -105,7 +106,8 @@
         mSamplingPeriodNs = samplingPeriodNs;
         mBatchReportLatencyNs = maxBatchReportLatencyNs;
         if (mEnabled) {
-            mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+            return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled,
+                    mSamplingPeriodNs, mBatchReportLatencyNs);
         }
     }
     return OK;
@@ -115,7 +117,8 @@
     if (mSamplingPeriodNs != ns) {
         mSamplingPeriodNs = ns;
         if (mEnabled) {
-            mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+            return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled,
+                    mSamplingPeriodNs, mBatchReportLatencyNs);
         }
     }
     return OK;
diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h
index 5ee5e12..c446d61 100644
--- a/services/sensorservice/SensorInterface.h
+++ b/services/sensorservice/SensorInterface.h
@@ -108,12 +108,12 @@
 public:
     static constexpr int DEFAULT_DEVICE_ID = 0;
 
-    class StateChangeCallback : public virtual RefBase {
+    class SensorCallback : public virtual RefBase {
       public:
-        virtual void onStateChanged(bool enabled, int64_t samplingPeriodNs,
-                                    int64_t batchReportLatencyNs) = 0;
+        virtual status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs,
+                                                int64_t batchReportLatencyNs) = 0;
     };
-    RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback);
+    RuntimeSensor(const sensor_t& sensor, sp<SensorCallback> callback);
     virtual status_t activate(void* ident, bool enabled) override;
     virtual status_t batch(void* ident, int handle, int flags, int64_t samplingPeriodNs,
                            int64_t maxBatchReportLatencyNs) override;
@@ -125,7 +125,7 @@
     bool mEnabled = false;
     int64_t mSamplingPeriodNs = 0;
     int64_t mBatchReportLatencyNs = 0;
-    sp<StateChangeCallback> mCallback;
+    sp<SensorCallback> mCallback;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 5c98614..3a0329c 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -116,16 +116,17 @@
     return nextHandle++;
 }
 
-class RuntimeSensorCallbackProxy : public RuntimeSensor::StateChangeCallback {
+class RuntimeSensorCallbackProxy : public RuntimeSensor::SensorCallback {
  public:
-    RuntimeSensorCallbackProxy(sp<SensorService::RuntimeSensorStateChangeCallback> callback)
+    RuntimeSensorCallbackProxy(sp<SensorService::RuntimeSensorCallback> callback)
         : mCallback(std::move(callback)) {}
-    void onStateChanged(bool enabled, int64_t samplingPeriodNs,
-                        int64_t batchReportLatencyNs) override {
-        mCallback->onStateChanged(enabled, samplingPeriodNs, batchReportLatencyNs);
+    status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs,
+                                    int64_t batchReportLatencyNs) override {
+        return mCallback->onConfigurationChanged(handle, enabled, samplingPeriodNs,
+                batchReportLatencyNs);
     }
  private:
-    sp<SensorService::RuntimeSensorStateChangeCallback> mCallback;
+    sp<SensorService::RuntimeSensorCallback> mCallback;
 };
 
 } // namespace
@@ -166,7 +167,7 @@
 }
 
 int SensorService::registerRuntimeSensor(
-    const sensor_t& sensor, int deviceId, sp<RuntimeSensorStateChangeCallback> callback) {
+        const sensor_t& sensor, int deviceId, sp<RuntimeSensorCallback> callback) {
     int handle = 0;
     while (handle == 0 || !mSensors.isNewHandle(handle)) {
         handle = nextRuntimeSensorHandle();
@@ -179,7 +180,7 @@
     ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s",
             handle, sensor.type, sensor.name);
 
-    sp<RuntimeSensor::StateChangeCallback> runtimeSensorCallback(
+    sp<RuntimeSensor::SensorCallback> runtimeSensorCallback(
         new RuntimeSensorCallbackProxy(std::move(callback)));
     sensor_t runtimeSensor = sensor;
     // force the handle to be consistent
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 0798279..3f6a895 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -147,12 +147,13 @@
         virtual void onProximityActive(bool isActive) = 0;
     };
 
-    class RuntimeSensorStateChangeCallback : public virtual RefBase {
+    class RuntimeSensorCallback : public virtual RefBase {
     public:
         // Note that the callback is invoked from an async thread and can interact with the
         // SensorService directly.
-        virtual void onStateChanged(bool enabled, int64_t samplingPeriodNanos,
-                                    int64_t batchReportLatencyNanos) = 0;
+        virtual status_t onConfigurationChanged(int handle, bool enabled,
+                                                int64_t samplingPeriodNanos,
+                                                int64_t batchReportLatencyNanos) = 0;
     };
 
     static char const* getServiceName() ANDROID_API { return "sensorservice"; }
@@ -182,7 +183,7 @@
     status_t removeProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
 
     int registerRuntimeSensor(const sensor_t& sensor, int deviceId,
-                              sp<RuntimeSensorStateChangeCallback> callback) ANDROID_API;
+                              sp<RuntimeSensorCallback> callback) ANDROID_API;
     status_t unregisterRuntimeSensor(int handle) ANDROID_API;
     status_t sendRuntimeSensorEvent(const sensors_event_t& event) ANDROID_API;
 
diff --git a/services/stats/.clang-format b/services/stats/.clang-format
new file mode 100644
index 0000000..cead3a0
--- /dev/null
+++ b/services/stats/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+  - Regex:    '^"Log\.h"'
+    Priority:    -1
diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp
index 410a5af..0f01507 100644
--- a/services/stats/StatsAidl.cpp
+++ b/services/stats/StatsAidl.cpp
@@ -14,57 +14,130 @@
  * limitations under the License.
  */
 
-#define DEBUG false // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #define LOG_TAG "StatsAidl"
 
-#include <log/log.h>
-#include <statslog.h>
+#define VLOG(...) \
+    if (DEBUG) ALOGD(__VA_ARGS__);
 
 #include "StatsAidl.h"
 
+#include <log/log.h>
+#include <stats_annotations.h>
+#include <stats_event.h>
+#include <statslog.h>
+
+#include <unordered_map>
+
 namespace aidl {
 namespace android {
 namespace frameworks {
 namespace stats {
 
-StatsHal::StatsHal() {}
+template <typename E>
+constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
+    return static_cast<typename std::underlying_type<E>::type>(e);
+}
+
+StatsHal::StatsHal() {
+}
+
+bool write_annotation(AStatsEvent* event, const Annotation& annotation) {
+    switch (annotation.value.getTag()) {
+        case AnnotationValue::boolValue: {
+            AStatsEvent_addBoolAnnotation(event, to_underlying(annotation.annotationId),
+                                          annotation.value.get<AnnotationValue::boolValue>());
+            break;
+        }
+        case AnnotationValue::intValue: {
+            AStatsEvent_addInt32Annotation(event, to_underlying(annotation.annotationId),
+                                           annotation.value.get<AnnotationValue::intValue>());
+            break;
+        }
+        default: {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool write_atom_annotations(AStatsEvent* event,
+                            const std::vector<std::optional<Annotation>>& annotations) {
+    for (const auto& atomAnnotation : annotations) {
+        if (!atomAnnotation) {
+            return false;
+        }
+        if (!write_annotation(event, *atomAnnotation)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool write_field_annotations(AStatsEvent* event, const std::vector<Annotation>& annotations) {
+    for (const auto& fieldAnnotation : annotations) {
+        if (!write_annotation(event, fieldAnnotation)) {
+            return false;
+        }
+    }
+    return true;
+}
 
 ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
     if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
-        ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+        ALOGE("Atom ID %ld is not a valid vendor atom ID", (long)vendorAtom.atomId);
         return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
-            -1, "Not a valid vendor atom ID");
+                -1, "Not a valid vendor atom ID");
     }
     if (vendorAtom.reverseDomainName.length() > 50) {
         ALOGE("Vendor atom reverse domain name %s is too long.",
-            vendorAtom.reverseDomainName.c_str());
+              vendorAtom.reverseDomainName.c_str());
         return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
-            -1, "Vendor atom reverse domain name is too long");
+                -1, "Vendor atom reverse domain name is too long");
     }
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, vendorAtom.atomId);
+
+    if (vendorAtom.atomAnnotations) {
+        if (!write_atom_annotations(event, *vendorAtom.atomAnnotations)) {
+            ALOGE("Atom ID %ld has incompatible atom level annotation", (long)vendorAtom.atomId);
+            AStatsEvent_release(event);
+            return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                    -1, "invalid atom annotation");
+        }
+    }
+
+    // populate map for quickier access for VendorAtomValue associated annotations by value index
+    std::unordered_map<int, int> fieldIndexToAnnotationSetMap;
+    if (vendorAtom.valuesAnnotations) {
+        const std::vector<std::optional<AnnotationSet>>& valuesAnnotations =
+                *vendorAtom.valuesAnnotations;
+        for (int i = 0; i < valuesAnnotations.size(); i++) {
+            if (valuesAnnotations[i]) {
+                fieldIndexToAnnotationSetMap[valuesAnnotations[i]->valueIndex] = i;
+            }
+        }
+    }
+
     AStatsEvent_writeString(event, vendorAtom.reverseDomainName.c_str());
+    size_t atomValueIdx = 0;
     for (const auto& atomValue : vendorAtom.values) {
         switch (atomValue.getTag()) {
             case VendorAtomValue::intValue:
-                AStatsEvent_writeInt32(event,
-                    atomValue.get<VendorAtomValue::intValue>());
+                AStatsEvent_writeInt32(event, atomValue.get<VendorAtomValue::intValue>());
                 break;
             case VendorAtomValue::longValue:
-                AStatsEvent_writeInt64(event,
-                    atomValue.get<VendorAtomValue::longValue>());
+                AStatsEvent_writeInt64(event, atomValue.get<VendorAtomValue::longValue>());
                 break;
             case VendorAtomValue::floatValue:
-                AStatsEvent_writeFloat(event,
-                    atomValue.get<VendorAtomValue::floatValue>());
+                AStatsEvent_writeFloat(event, atomValue.get<VendorAtomValue::floatValue>());
                 break;
             case VendorAtomValue::stringValue:
                 AStatsEvent_writeString(event,
-                    atomValue.get<VendorAtomValue::stringValue>().c_str());
+                                        atomValue.get<VendorAtomValue::stringValue>().c_str());
                 break;
             case VendorAtomValue::boolValue:
-                AStatsEvent_writeBool(event,
-                    atomValue.get<VendorAtomValue::boolValue>());
+                AStatsEvent_writeBool(event, atomValue.get<VendorAtomValue::boolValue>());
                 break;
             case VendorAtomValue::repeatedIntValue: {
                 const std::optional<std::vector<int>>& repeatedIntValue =
@@ -112,8 +185,8 @@
 
                 for (int i = 0; i < repeatedStringVector.size(); ++i) {
                     cStringArray[i] = repeatedStringVector[i].has_value()
-                            ? repeatedStringVector[i]->c_str()
-                            : "";
+                                              ? repeatedStringVector[i]->c_str()
+                                              : "";
                 }
 
                 AStatsEvent_writeStringArray(event, cStringArray, repeatedStringVector.size());
@@ -146,15 +219,40 @@
                 AStatsEvent_writeByteArray(event, byteArrayValue->data(), byteArrayValue->size());
                 break;
             }
+            default: {
+                AStatsEvent_release(event);
+                ALOGE("Atom ID %ld has invalid atomValue.getTag", (long)vendorAtom.atomId);
+                return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                        -1, "invalid atomValue.getTag");
+                break;
+            }
         }
+
+        const auto& valueAnnotationIndex = fieldIndexToAnnotationSetMap.find(atomValueIdx);
+        if (valueAnnotationIndex != fieldIndexToAnnotationSetMap.end()) {
+            const std::vector<Annotation>& fieldAnnotations =
+                    (*vendorAtom.valuesAnnotations)[valueAnnotationIndex->second]->annotations;
+            VLOG("Atom ID %ld has %ld annotations for field #%ld", (long)vendorAtom.atomId,
+                 (long)fieldAnnotations.size(), (long)atomValueIdx + 2);
+            if (!write_field_annotations(event, fieldAnnotations)) {
+                ALOGE("Atom ID %ld has incompatible field level annotation for field #%ld",
+                      (long)vendorAtom.atomId, (long)atomValueIdx + 2);
+                AStatsEvent_release(event);
+                return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                        -1, "invalid atom field annotation");
+            }
+        }
+        atomValueIdx++;
     }
     AStatsEvent_build(event);
     const int ret = AStatsEvent_write(event);
     AStatsEvent_release(event);
-
-    return ret <= 0 ?
-            ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret, "report atom failed") :
-            ndk::ScopedAStatus::ok();
+    if (ret <= 0) {
+        ALOGE("Error writing Atom ID %ld. Result: %d", (long)vendorAtom.atomId, ret);
+    }
+    return ret <= 0 ? ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret,
+                                                                              "report atom failed")
+                    : ndk::ScopedAStatus::ok();
 }
 
 }  // namespace stats
diff --git a/services/stats/StatsHal.cpp b/services/stats/StatsHal.cpp
index d27d989..19176d9 100644
--- a/services/stats/StatsHal.cpp
+++ b/services/stats/StatsHal.cpp
@@ -14,42 +14,42 @@
  * limitations under the License.
  */
 
-#define DEBUG false // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #define LOG_TAG "StatsHal"
 
+#include "StatsHal.h"
+
 #include <log/log.h>
 #include <statslog.h>
 
-#include "StatsHal.h"
-
 namespace android {
 namespace frameworks {
 namespace stats {
 namespace V1_0 {
 namespace implementation {
 
-StatsHal::StatsHal() {}
+StatsHal::StatsHal() {
+}
 
-hardware::Return<void> StatsHal::reportSpeakerImpedance(
-        const SpeakerImpedance& speakerImpedance) {
+hardware::Return<void> StatsHal::reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) {
     android::util::stats_write(android::util::SPEAKER_IMPEDANCE_REPORTED,
-            speakerImpedance.speakerLocation, speakerImpedance.milliOhms);
+                               speakerImpedance.speakerLocation, speakerImpedance.milliOhms);
 
     return hardware::Void();
 }
 
 hardware::Return<void> StatsHal::reportHardwareFailed(const HardwareFailed& hardwareFailed) {
     android::util::stats_write(android::util::HARDWARE_FAILED, int32_t(hardwareFailed.hardwareType),
-            hardwareFailed.hardwareLocation, int32_t(hardwareFailed.errorCode));
+                               hardwareFailed.hardwareLocation, int32_t(hardwareFailed.errorCode));
 
     return hardware::Void();
 }
 
 hardware::Return<void> StatsHal::reportPhysicalDropDetected(
         const PhysicalDropDetected& physicalDropDetected) {
-    android::util::stats_write(android::util::PHYSICAL_DROP_DETECTED,
-            int32_t(physicalDropDetected.confidencePctg), physicalDropDetected.accelPeak,
-            physicalDropDetected.freefallDuration);
+    android::util::stats_write(
+            android::util::PHYSICAL_DROP_DETECTED, int32_t(physicalDropDetected.confidencePctg),
+            physicalDropDetected.accelPeak, physicalDropDetected.freefallDuration);
 
     return hardware::Void();
 }
@@ -58,20 +58,21 @@
     std::vector<int32_t> buckets = chargeCycles.cycleBucket;
     int initialSize = buckets.size();
     for (int i = 0; i < 10 - initialSize; i++) {
-        buckets.push_back(0); // Push 0 for buckets that do not exist.
+        buckets.push_back(0);  // Push 0 for buckets that do not exist.
     }
     android::util::stats_write(android::util::CHARGE_CYCLES_REPORTED, buckets[0], buckets[1],
-            buckets[2], buckets[3], buckets[4], buckets[5], buckets[6], buckets[7], buckets[8],
-            buckets[9]);
+                               buckets[2], buckets[3], buckets[4], buckets[5], buckets[6],
+                               buckets[7], buckets[8], buckets[9]);
 
     return hardware::Void();
 }
 
 hardware::Return<void> StatsHal::reportBatteryHealthSnapshot(
         const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) {
-    android::util::stats_write(android::util::BATTERY_HEALTH_SNAPSHOT,
-            int32_t(batteryHealthSnapshotArgs.type), batteryHealthSnapshotArgs.temperatureDeciC,
-            batteryHealthSnapshotArgs.voltageMicroV, batteryHealthSnapshotArgs.currentMicroA,
+    android::util::stats_write(
+            android::util::BATTERY_HEALTH_SNAPSHOT, int32_t(batteryHealthSnapshotArgs.type),
+            batteryHealthSnapshotArgs.temperatureDeciC, batteryHealthSnapshotArgs.voltageMicroV,
+            batteryHealthSnapshotArgs.currentMicroA,
             batteryHealthSnapshotArgs.openCircuitVoltageMicroV,
             batteryHealthSnapshotArgs.resistanceMicroOhm, batteryHealthSnapshotArgs.levelPercent);
 
@@ -87,14 +88,15 @@
 hardware::Return<void> StatsHal::reportBatteryCausedShutdown(
         const BatteryCausedShutdown& batteryCausedShutdown) {
     android::util::stats_write(android::util::BATTERY_CAUSED_SHUTDOWN,
-            batteryCausedShutdown.voltageMicroV);
+                               batteryCausedShutdown.voltageMicroV);
 
     return hardware::Void();
 }
 
 hardware::Return<void> StatsHal::reportUsbPortOverheatEvent(
         const UsbPortOverheatEvent& usbPortOverheatEvent) {
-    android::util::stats_write(android::util::USB_PORT_OVERHEAT_EVENT_REPORTED,
+    android::util::stats_write(
+            android::util::USB_PORT_OVERHEAT_EVENT_REPORTED,
             usbPortOverheatEvent.plugTemperatureDeciC, usbPortOverheatEvent.maxTemperatureDeciC,
             usbPortOverheatEvent.timeToOverheat, usbPortOverheatEvent.timeToHysteresis,
             usbPortOverheatEvent.timeToInactive);
@@ -102,18 +104,17 @@
     return hardware::Void();
 }
 
-hardware::Return<void> StatsHal::reportSpeechDspStat(
-        const SpeechDspStat& speechDspStat) {
+hardware::Return<void> StatsHal::reportSpeechDspStat(const SpeechDspStat& speechDspStat) {
     android::util::stats_write(android::util::SPEECH_DSP_STAT_REPORTED,
-            speechDspStat.totalUptimeMillis, speechDspStat.totalDowntimeMillis,
-            speechDspStat.totalCrashCount, speechDspStat.totalRecoverCount);
+                               speechDspStat.totalUptimeMillis, speechDspStat.totalDowntimeMillis,
+                               speechDspStat.totalCrashCount, speechDspStat.totalRecoverCount);
 
     return hardware::Void();
 }
 
 hardware::Return<void> StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
     if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
-        ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+        ALOGE("Atom ID %ld is not a valid vendor atom ID", (long)vendorAtom.atomId);
         return hardware::Void();
     }
     if (vendorAtom.reverseDomainName.size() > 50) {
diff --git a/services/stats/include/stats/StatsAidl.h b/services/stats/include/stats/StatsAidl.h
index 219e71e..340b539 100644
--- a/services/stats/include/stats/StatsAidl.h
+++ b/services/stats/include/stats/StatsAidl.h
@@ -28,8 +28,7 @@
     /**
      * Binder call to get vendor atom.
      */
-    virtual ndk::ScopedAStatus reportVendorAtom(
-        const VendorAtom& in_vendorAtom) override;
+    virtual ndk::ScopedAStatus reportVendorAtom(const VendorAtom& in_vendorAtom) override;
 };
 
 }  // namespace stats
diff --git a/services/stats/include/stats/StatsHal.h b/services/stats/include/stats/StatsHal.h
index 071e54f..864ad14 100644
--- a/services/stats/include/stats/StatsHal.h
+++ b/services/stats/include/stats/StatsHal.h
@@ -16,7 +16,6 @@
 
 #include <android/frameworks/stats/1.0/IStats.h>
 #include <android/frameworks/stats/1.0/types.h>
-
 #include <stats_event.h>
 
 using namespace android::frameworks::stats::V1_0;
@@ -30,8 +29,8 @@
 using android::hardware::Return;
 
 /**
-* Implements the Stats HAL
-*/
+ * Implements the Stats HAL
+ */
 class StatsHal : public IStats {
 public:
     StatsHal();
@@ -50,12 +49,12 @@
      * Binder call to get PhysicalDropDetected atom.
      */
     virtual Return<void> reportPhysicalDropDetected(
-             const PhysicalDropDetected& physicalDropDetected) override;
+            const PhysicalDropDetected& physicalDropDetected) override;
 
     /**
      * Binder call to get ChargeCyclesReported atom.
      */
-     virtual Return<void> reportChargeCycles(const ChargeCycles& chargeCycles) override;
+    virtual Return<void> reportChargeCycles(const ChargeCycles& chargeCycles) override;
 
     /**
      * Binder call to get BatteryHealthSnapshot atom.
@@ -83,8 +82,7 @@
     /**
      * Binder call to get Speech DSP state atom.
      */
-    virtual Return<void> reportSpeechDspStat(
-            const SpeechDspStat& speechDspStat) override;
+    virtual Return<void> reportSpeechDspStat(const SpeechDspStat& speechDspStat) override;
 
     /**
      * Binder call to get vendor atom.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index ad98e93..5bb2497 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -209,6 +209,9 @@
     // The dimming flag
     bool dimmingEnabled{true};
 
+    float currentSdrHdrRatio = 1.f;
+    float desiredSdrHdrRatio = 1.f;
+
     virtual ~LayerFECompositionState();
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 8ec77c0..229a657 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -136,9 +136,8 @@
     compositionengine::Output::FrameFences presentAndGetFrameFences() override;
     virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const;
     std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
-          bool supportsProtectedContent, ui::Dataspace outputDataspace,
-          std::vector<LayerFE*> &outLayerFEs) override;
-    virtual bool layerNeedsFiltering(const OutputLayer*) const;
+            bool supportsProtectedContent, ui::Dataspace outputDataspace,
+            std::vector<LayerFE*>& outLayerFEs) override;
     void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index a405c4d..531b659 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -121,6 +121,10 @@
     dumpVal(out, "dataspace", toString(dataspace), dataspace);
     dumpVal(out, "hdr metadata types", hdrMetadata.validTypes);
     dumpVal(out, "dimming enabled", dimmingEnabled);
+    if (currentSdrHdrRatio > 1.01f || desiredSdrHdrRatio > 1.01f) {
+        dumpVal(out, "current sdr/hdr ratio", currentSdrHdrRatio);
+        dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio);
+    }
     dumpVal(out, "colorTransform", colorTransform);
 
     out.append("\n");
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3ec6816..403385e 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1438,7 +1438,7 @@
                                              Enabled);
                 compositionengine::LayerFE::ClientCompositionTargetSettings
                         targetSettings{.clip = clip,
-                                       .needsFiltering = layerNeedsFiltering(layer) ||
+                                       .needsFiltering = layer->needsFiltering() ||
                                                outputState.needsFiltering,
                                        .isSecure = outputState.isSecure,
                                        .supportsProtectedContent = supportsProtectedContent,
@@ -1469,10 +1469,6 @@
     return clientCompositionLayers;
 }
 
-bool Output::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
-    return layer->needsFiltering();
-}
-
 void Output::appendRegionFlashRequests(
         const Region& flashRegion, std::vector<LayerFE::LayerSettings>& clientCompositionLayers) {
     if (flashRegion.isEmpty()) {
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 60a2c83..1b86cd3 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -340,10 +340,17 @@
         state.dimmingRatio = 1.f;
         state.whitePointNits = getOutput().getState().displayBrightnessNits;
     } else {
-        state.dimmingRatio = std::clamp(getOutput().getState().sdrWhitePointNits /
-                                                getOutput().getState().displayBrightnessNits,
-                                        0.f, 1.f);
-        state.whitePointNits = getOutput().getState().sdrWhitePointNits;
+        float layerBrightnessNits = getOutput().getState().sdrWhitePointNits;
+        // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular
+        // range that we may need to re-adjust to the current display conditions
+        if ((state.dataspace & HAL_DATASPACE_RANGE_MASK) == HAL_DATASPACE_RANGE_EXTENDED &&
+            layerFEState->currentSdrHdrRatio > 1.01f) {
+            layerBrightnessNits *= layerFEState->currentSdrHdrRatio;
+        }
+        state.dimmingRatio =
+                std::clamp(layerBrightnessNits / getOutput().getState().displayBrightnessNits, 0.f,
+                           1.f);
+        state.whitePointNits = layerBrightnessNits;
     }
 
     // These are evaluated every frame as they can potentially change at any
@@ -578,6 +585,7 @@
         case Composition::CURSOR:
         case Composition::DEVICE:
         case Composition::DISPLAY_DECORATION:
+        case Composition::REFRESH_RATE_INDICATOR:
             writeBufferStateToHWC(hwcLayer, outputIndependentState, skipLayer);
             break;
         case Composition::INVALID:
@@ -773,6 +781,7 @@
         case Composition::CURSOR:
         case Composition::SIDEBAND:
         case Composition::DISPLAY_DECORATION:
+        case Composition::REFRESH_RATE_INDICATOR:
             result = (to == Composition::CLIENT || to == Composition::DEVICE);
             break;
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
index 2fc029f..6064126 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -151,6 +151,10 @@
                 // A for "Alpha", since the decoration is an alpha layer.
                 result.append("A");
                 break;
+            case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR:
+                // R for "Refresh", since the layer is Refresh rate overlay.
+                result.append("R");
+                break;
         }
     }
     return result;
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 6199a5a..933f616 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -93,7 +93,7 @@
     MOCK_METHOD2(onHotplug,
                  std::optional<DisplayIdentificationInfo>(hal::HWDisplayId, hal::Connection));
     MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool());
-    MOCK_METHOD2(onVsync, bool(hal::HWDisplayId, int64_t));
+    MOCK_METHOD(std::optional<PhysicalDisplayId>, onVsync, (hal::HWDisplayId, int64_t));
     MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync));
     MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId));
     MOCK_CONST_METHOD1(getModes, std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId));
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index c61f7d8..5f73fbc 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -71,6 +71,7 @@
         mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())),
         mPhysicalOrientation(args.physicalOrientation),
         mIsPrimary(args.isPrimary),
+        mRequestedRefreshRate(args.requestedRefreshRate),
         mRefreshRateSelector(std::move(args.refreshRateSelector)) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
     mCompositionDisplay->createRenderSurface(
@@ -347,10 +348,6 @@
     return mCompositionDisplay->getState().undefinedRegion;
 }
 
-bool DisplayDevice::needsFiltering() const {
-    return mCompositionDisplay->getState().needsFiltering;
-}
-
 ui::LayerStack DisplayDevice::getLayerStack() const {
     return mCompositionDisplay->getState().layerFilter.layerStack;
 }
@@ -513,6 +510,23 @@
     mDesiredActiveModeChanged = false;
 }
 
+void DisplayDevice::adjustRefreshRate(Fps leaderDisplayRefreshRate) {
+    using fps_approx_ops::operator==;
+    if (mRequestedRefreshRate == 0_Hz) {
+        return;
+    }
+
+    using fps_approx_ops::operator>;
+    if (mRequestedRefreshRate > leaderDisplayRefreshRate) {
+        mAdjustedRefreshRate = leaderDisplayRefreshRate;
+        return;
+    }
+
+    unsigned divisor = static_cast<unsigned>(
+            std::round(leaderDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue()));
+    mAdjustedRefreshRate = leaderDisplayRefreshRate / divisor;
+}
+
 std::atomic<int32_t> DisplayDeviceState::sNextSequenceId(1);
 
 }  // namespace android
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 370bd66..b86d9be 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -24,6 +24,7 @@
 #include <android-base/thread_annotations.h>
 #include <android/native_window.h>
 #include <binder/IBinder.h>
+#include <ftl/concat.h>
 #include <gui/LayerState.h>
 #include <math/mat4.h>
 #include <renderengine/RenderEngine.h>
@@ -115,7 +116,6 @@
     const ui::Transform& getTransform() const;
     const Rect& getLayerStackSpaceRect() const;
     const Rect& getOrientedDisplaySpaceRect() const;
-    bool needsFiltering() const;
     ui::LayerStack getLayerStack() const;
     bool receivesInput() const { return mFlags & eReceivesInput; }
 
@@ -245,6 +245,12 @@
 
     nsecs_t getVsyncPeriodFromHWC() const;
 
+    Fps getAdjustedRefreshRate() const { return mAdjustedRefreshRate; }
+
+    // Round the requested refresh rate to match a divisor of the leader
+    // display's refresh rate. Only supported for virtual displays.
+    void adjustRefreshRate(Fps leaderDisplayRefreshRate);
+
     // release HWC resources (if any) for removable displays
     void disconnect();
 
@@ -279,6 +285,15 @@
 
     uint32_t mFlags = 0;
 
+    // Requested refresh rate in fps, supported only for virtual displays.
+    // when this value is non zero, SurfaceFlinger will try to drop frames
+    // for virtual displays to match this requested refresh rate.
+    const Fps mRequestedRefreshRate;
+
+    // Adjusted refresh rate, rounded to match a divisor of the leader
+    // display's refresh rate. Only supported for virtual displays.
+    Fps mAdjustedRefreshRate = 0_Hz;
+
     std::vector<ui::Hdr> mOverrideHdrTypes;
 
     std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
@@ -286,8 +301,8 @@
 
     mutable std::mutex mActiveModeLock;
     ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
-    TracedOrdinal<bool> mDesiredActiveModeChanged
-            GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false};
+    TracedOrdinal<bool> mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock) =
+            {ftl::Concat("DesiredActiveModeChanged-", getId().value).c_str(), false};
     ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
 };
 
@@ -316,6 +331,8 @@
     uint32_t height = 0;
     std::string displayName;
     bool isSecure = false;
+    // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
+    Fps requestedRefreshRate;
 
 private:
     static std::atomic<int32_t> sNextSequenceId;
@@ -345,6 +362,8 @@
     std::optional<hardware::graphics::composer::hal::PowerMode> initialPowerMode;
     bool isPrimary{false};
     DisplayModeId activeModeId;
+    // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
+    Fps requestedRefreshRate;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 9470552..ba9aed8 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -208,6 +208,12 @@
         return ::ndk::ScopedAStatus::ok();
     }
 
+    ::ndk::ScopedAStatus onRefreshRateChangedDebug(
+            const RefreshRateChangedDebugData& refreshRateChangedDebugData) override {
+        mCallback.onRefreshRateChangedDebug(refreshRateChangedDebugData);
+        return ::ndk::ScopedAStatus::ok();
+    }
+
 private:
     HWC2::ComposerCallback& mCallback;
 };
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index c1c7070..23dd3e5 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -44,6 +44,7 @@
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
+#include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
 
 namespace android {
 
@@ -63,6 +64,8 @@
 
 namespace hal = android::hardware::graphics::composer::hal;
 
+using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
+
 // Implement this interface to receive hardware composer events.
 //
 // These callback functions will generally be called on a hwbinder thread, but
@@ -77,6 +80,7 @@
                                                        const hal::VsyncPeriodChangeTimeline&) = 0;
     virtual void onComposerHalSeamlessPossible(hal::HWDisplayId) = 0;
     virtual void onComposerHalVsyncIdle(hal::HWDisplayId) = 0;
+    virtual void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) = 0;
 
 protected:
     ~ComposerCallback() = default;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 7dde6b4..6d94079 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -30,6 +30,7 @@
 #include <compositionengine/Output.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <ftl/concat.h>
 #include <log/log.h>
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
@@ -148,16 +149,17 @@
     return mUpdateDeviceProductInfoOnHotplugReconnect;
 }
 
-bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) {
-    const auto displayId = toPhysicalDisplayId(hwcDisplayId);
-    if (!displayId) {
+std::optional<PhysicalDisplayId> HWComposer::onVsync(hal::HWDisplayId hwcDisplayId,
+                                                     nsecs_t timestamp) {
+    const auto displayIdOpt = toPhysicalDisplayId(hwcDisplayId);
+    if (!displayIdOpt) {
         LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display");
-        return false;
+        return {};
     }
 
-    RETURN_IF_INVALID_DISPLAY(*displayId, false);
+    RETURN_IF_INVALID_DISPLAY(*displayIdOpt, {});
 
-    auto& displayData = mDisplayData[*displayId];
+    auto& displayData = mDisplayData[*displayIdOpt];
 
     {
         // There have been reports of HWCs that signal several vsync events
@@ -166,18 +168,18 @@
         // out here so they don't cause havoc downstream.
         if (timestamp == displayData.lastPresentTimestamp) {
             ALOGW("Ignoring duplicate VSYNC event from HWC for display %s (t=%" PRId64 ")",
-                  to_string(*displayId).c_str(), timestamp);
-            return false;
+                  to_string(*displayIdOpt).c_str(), timestamp);
+            return {};
         }
 
         displayData.lastPresentTimestamp = timestamp;
     }
 
-    const auto tag = "HW_VSYNC_" + to_string(*displayId);
-    ATRACE_INT(tag.c_str(), displayData.vsyncTraceToggle);
+    ATRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(),
+               displayData.vsyncTraceToggle);
     displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle;
 
-    return true;
+    return displayIdOpt;
 }
 
 size_t HWComposer::getMaxVirtualDisplayCount() const {
@@ -375,8 +377,8 @@
 
     displayData.vsyncEnabled = enabled;
 
-    const auto tag = "HW_VSYNC_ON_" + to_string(displayId);
-    ATRACE_INT(tag.c_str(), enabled == hal::Vsync::ENABLE ? 1 : 0);
+    ATRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(),
+               enabled == hal::Vsync::ENABLE ? 1 : 0);
 }
 
 status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot,
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index f6155d2..acebfb2 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -221,7 +221,10 @@
     // TODO(b/157555476): Remove when the framework has proper support for headless mode
     virtual bool updatesDeviceProductInfoOnHotplugReconnect() const = 0;
 
-    virtual bool onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0;
+    // Called when a vsync happens. If the vsync is valid, returns the
+    // corresponding PhysicalDisplayId. Otherwise returns nullopt.
+    virtual std::optional<PhysicalDisplayId> onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0;
+
     virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0;
 
     virtual bool isConnected(PhysicalDisplayId) const = 0;
@@ -402,7 +405,7 @@
 
     bool updatesDeviceProductInfoOnHotplugReconnect() const override;
 
-    bool onVsync(hal::HWDisplayId, nsecs_t timestamp) override;
+    std::optional<PhysicalDisplayId> onVsync(hal::HWDisplayId, nsecs_t timestamp) override;
     void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override;
 
     bool isConnected(PhysicalDisplayId) const override;
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 537d545..bf3089f 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -113,6 +113,8 @@
             return "Sideband";
         case aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION:
             return "DisplayDecoration";
+        case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR:
+            return "RefreshRateIndicator";
         default:
             return "Unknown";
     }
diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp
index 20486e0..8f39e26 100644
--- a/services/surfaceflinger/DisplayRenderArea.cpp
+++ b/services/surfaceflinger/DisplayRenderArea.cpp
@@ -48,8 +48,8 @@
 DisplayRenderArea::DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop,
                                      ui::Size reqSize, ui::Dataspace reqDataSpace,
                                      bool useIdentityTransform, bool allowSecureLayers)
-      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, display->getLayerStackSpaceRect(),
-                   allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)),
+      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, allowSecureLayers,
+                   applyDeviceOrientation(useIdentityTransform, *display)),
         mDisplay(std::move(display)),
         mSourceCrop(sourceCrop) {}
 
@@ -57,18 +57,6 @@
     return mTransform;
 }
 
-Rect DisplayRenderArea::getBounds() const {
-    return mDisplay->getBounds();
-}
-
-int DisplayRenderArea::getHeight() const {
-    return mDisplay->getHeight();
-}
-
-int DisplayRenderArea::getWidth() const {
-    return mDisplay->getWidth();
-}
-
 bool DisplayRenderArea::isSecure() const {
     return mAllowSecureLayers && mDisplay->isSecure();
 }
@@ -77,18 +65,6 @@
     return mDisplay;
 }
 
-bool DisplayRenderArea::needsFiltering() const {
-    // check if the projection from the logical render area
-    // to the physical render area requires filtering
-    const Rect& sourceCrop = getSourceCrop();
-    int width = sourceCrop.width();
-    int height = sourceCrop.height();
-    if (getRotationFlags() & ui::Transform::ROT_90) {
-        std::swap(width, height);
-    }
-    return width != getReqWidth() || height != getReqHeight();
-}
-
 Rect DisplayRenderArea::getSourceCrop() const {
     // use the projected display viewport by default.
     if (mSourceCrop.isEmpty()) {
@@ -107,4 +83,4 @@
     return rotation.transform(mSourceCrop);
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h
index 3478fc1..ce5410a 100644
--- a/services/surfaceflinger/DisplayRenderArea.h
+++ b/services/surfaceflinger/DisplayRenderArea.h
@@ -33,12 +33,8 @@
                                               bool allowSecureLayers = true);
 
     const ui::Transform& getTransform() const override;
-    Rect getBounds() const override;
-    int getHeight() const override;
-    int getWidth() const override;
     bool isSecure() const override;
     sp<const DisplayDevice> getDisplayDevice() const override;
-    bool needsFiltering() const override;
     Rect getSourceCrop() const override;
 
 private:
@@ -50,4 +46,4 @@
     const ui::Transform mTransform;
 };
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 27a099c..925f111 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -1095,6 +1095,12 @@
 }
 
 void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const {
+    if (mSurfaceFrames.empty()) {
+        // We don't want to trace display frames without any surface frames updates as this cannot
+        // be janky
+        return;
+    }
+
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // DisplayFrame should not have an invalid token.
         ALOGE("Cannot trace DisplayFrame with invalid token");
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
index 7b5a157..9d2aaab 100644
--- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -18,10 +18,12 @@
 
 #include <binder/Binder.h>
 #include <gui/LayerMetadata.h>
+#include <ui/LayerStack.h>
 #include <utils/StrongPointer.h>
 #include <cstdint>
 #include <limits>
 #include <optional>
+
 constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits<uint32_t>::max();
 
 namespace android {
@@ -51,6 +53,7 @@
     bool addToRoot = true;
     wp<IBinder> parentHandle = nullptr;
     wp<IBinder> mirrorLayerHandle = nullptr;
+    ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK;
 };
 
 } // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 678d36b..a4fac1c 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -252,8 +252,8 @@
     attachToParent(hierarchy);
     attachToRelativeParent(hierarchy);
 
-    if (layer->mirrorId != UNASSIGNED_LAYER_ID) {
-        LayerHierarchy* mirror = getHierarchyFromId(layer->mirrorId);
+    for (uint32_t mirrorId : layer->mirrorIds) {
+        LayerHierarchy* mirror = getHierarchyFromId(mirrorId);
         hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror);
     }
 }
@@ -292,14 +292,14 @@
     auto it = hierarchy->mChildren.begin();
     while (it != hierarchy->mChildren.end()) {
         if (it->second == LayerHierarchy::Variant::Mirror) {
-            hierarchy->mChildren.erase(it);
-            break;
+            it = hierarchy->mChildren.erase(it);
+        } else {
+            it++;
         }
-        it++;
     }
 
-    if (layer->mirrorId != UNASSIGNED_LAYER_ID) {
-        hierarchy->addChild(getHierarchyFromId(layer->mirrorId), LayerHierarchy::Variant::Mirror);
+    for (uint32_t mirrorId : layer->mirrorIds) {
+        hierarchy->addChild(getHierarchyFromId(mirrorId), LayerHierarchy::Variant::Mirror);
     }
 }
 
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 5514c06..547a852 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -44,9 +44,28 @@
 
         layer.parentId = linkLayer(layer.parentId, layer.id);
         layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id);
-        layer.mirrorId = linkLayer(layer.mirrorId, layer.id);
+        if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+            // if this layer is mirroring a display, then walk though all the existing root layers
+            // for the layer stack and add them as children to be mirrored.
+            mDisplayMirroringLayers.emplace_back(layer.id);
+            for (auto& rootLayer : mLayers) {
+                if (rootLayer->isRoot() && rootLayer->layerStack == layer.layerStackToMirror) {
+                    layer.mirrorIds.emplace_back(rootLayer->id);
+                    linkLayer(rootLayer->id, layer.id);
+                }
+            }
+        } else {
+            // 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);
+            }
+        }
         layer.touchCropId = linkLayer(layer.touchCropId, layer.id);
-
+        if (layer.isRoot()) {
+            updateDisplayMirrorLayers(layer);
+        }
         mLayers.emplace_back(std::move(newLayer));
     }
 }
@@ -85,7 +104,14 @@
 
         layer.parentId = unlinkLayer(layer.parentId, layer.id);
         layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id);
-        layer.mirrorId = unlinkLayer(layer.mirrorId, layer.id);
+        if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+            layer.mirrorIds = unlinkLayers(layer.mirrorIds, layer.id);
+            swapErase(mDisplayMirroringLayers, layer.id);
+        } else {
+            layer.layerIdToMirror = unlinkLayer(layer.layerIdToMirror, layer.id);
+            layer.mirrorIds.clear();
+        }
+
         layer.touchCropId = unlinkLayer(layer.touchCropId, layer.id);
 
         auto& references = it->second.references;
@@ -106,8 +132,8 @@
             if (linkedLayer->relativeParentId == layer.id) {
                 linkedLayer->relativeParentId = UNASSIGNED_LAYER_ID;
             }
-            if (linkedLayer->mirrorId == layer.id) {
-                linkedLayer->mirrorId = UNASSIGNED_LAYER_ID;
+            if (swapErase(linkedLayer->mirrorIds, layer.id)) {
+                linkedLayer->changes |= RequestedLayerState::Changes::Mirror;
             }
             if (linkedLayer->touchCropId == layer.id) {
                 linkedLayer->touchCropId = UNASSIGNED_LAYER_ID;
@@ -200,6 +226,12 @@
             if (oldParentId != layer->parentId) {
                 unlinkLayer(oldParentId, layer->id);
                 layer->parentId = linkLayer(layer->parentId, layer->id);
+                if (oldParentId == UNASSIGNED_LAYER_ID) {
+                    updateDisplayMirrorLayers(*layer);
+                }
+            }
+            if (layer->what & layer_state_t::eLayerStackChanged && layer->isRoot()) {
+                updateDisplayMirrorLayers(*layer);
             }
             if (oldRelativeParentId != layer->relativeParentId) {
                 unlinkLayer(oldRelativeParentId, layer->id);
@@ -308,6 +340,14 @@
     return UNASSIGNED_LAYER_ID;
 }
 
+std::vector<uint32_t> LayerLifecycleManager::unlinkLayers(const std::vector<uint32_t>& layerIds,
+                                                          uint32_t linkedLayer) {
+    for (uint32_t layerId : layerIds) {
+        unlinkLayer(layerId, linkedLayer);
+    }
+    return {};
+}
+
 std::string LayerLifecycleManager::References::getDebugString() const {
     std::string debugInfo = owner.name + "[" + std::to_string(owner.id) + "] refs:";
     std::for_each(references.begin(), references.end(),
@@ -329,4 +369,30 @@
     mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
 }
 
+// Some layers mirror the entire display stack. Since we don't have a single root layer per display
+// we have to track all these layers and update what they mirror when the list of root layers
+// on a display changes. This function walks through the list of display mirroring layers
+// and updates its list of layers that its mirroring. This function should be called when a new
+// root layer is added, removed or moved to another display.
+void LayerLifecycleManager::updateDisplayMirrorLayers(RequestedLayerState& rootLayer) {
+    for (uint32_t mirrorLayerId : mDisplayMirroringLayers) {
+        RequestedLayerState* mirrorLayer = getLayerFromId(mirrorLayerId);
+        bool canBeMirrored =
+                rootLayer.isRoot() && rootLayer.layerStack == mirrorLayer->layerStackToMirror;
+        bool currentlyMirrored =
+                std::find(mirrorLayer->mirrorIds.begin(), mirrorLayer->mirrorIds.end(),
+                          rootLayer.id) != mirrorLayer->mirrorIds.end();
+
+        if (canBeMirrored && !currentlyMirrored) {
+            mirrorLayer->mirrorIds.emplace_back(rootLayer.id);
+            linkLayer(rootLayer.id, mirrorLayer->id);
+            mirrorLayer->changes |= RequestedLayerState::Changes::Mirror;
+        } else if (!canBeMirrored && currentlyMirrored) {
+            swapErase(mirrorLayer->mirrorIds, rootLayer.id);
+            unlinkLayer(rootLayer.id, mirrorLayer->id);
+            mirrorLayer->changes |= RequestedLayerState::Changes::Mirror;
+        }
+    }
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 63a7afc..25d27ee 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -80,6 +80,9 @@
     std::vector<uint32_t>* getLinkedLayersFromId(uint32_t);
     uint32_t linkLayer(uint32_t layerId, uint32_t layerToLink);
     uint32_t unlinkLayer(uint32_t layerId, uint32_t linkedLayer);
+    std::vector<uint32_t> unlinkLayers(const std::vector<uint32_t>& layerIds, uint32_t linkedLayer);
+
+    void updateDisplayMirrorLayers(RequestedLayerState& rootLayer);
 
     struct References {
         // Lifetime tied to mLayers
@@ -90,6 +93,8 @@
     std::unordered_map<uint32_t, References> mIdToLayer;
     // Listeners are invoked once changes are committed.
     std::vector<std::shared_ptr<ILifecycleListener>> mListeners;
+    // Layers that mirror a display stack (see updateDisplayMirrorLayers)
+    std::vector<uint32_t> mDisplayMirroringLayers;
 
     // Aggregation of changes since last commit.
     ftl::Flags<RequestedLayerState::Changes> mGlobalChanges;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 3a0540c..dbb7fbf 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -112,6 +112,10 @@
 }
 
 bool LayerSnapshot::getIsVisible() const {
+    if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) {
+        return false;
+    }
+
     if (!hasSomethingToDraw()) {
         return false;
     }
@@ -125,6 +129,7 @@
 
 std::string LayerSnapshot::getIsVisibleReason() const {
     // not visible
+    if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) return "eLayerSkipScreenshot";
     if (!hasSomethingToDraw()) return "!hasSomethingToDraw";
     if (invalidTransform) return "invalidTransform";
     if (isHiddenByPolicyFromParent) return "hidden by parent or layer flag";
@@ -163,4 +168,11 @@
     return debug.str();
 }
 
+FloatRect LayerSnapshot::sourceBounds() const {
+    if (!externalTexture) {
+        return geomLayerBounds;
+    }
+    return geomBufferSize.toFloatRect();
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 4512ade..5d74203 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -68,7 +68,6 @@
     renderengine::ShadowSettings shadowSettings;
     bool premultipliedAlpha;
     bool isHdrY410;
-    bool bufferNeedsFiltering;
     ui::Transform parentTransform;
     Rect bufferSize;
     Rect croppedBufferSize;
@@ -84,6 +83,7 @@
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
     ui::Transform::RotationFlags fixedTransformHint;
+    bool handleSkipScreenshotFlag = false;
     ChildState childState;
 
     static bool isOpaqueFormat(PixelFormat format);
@@ -102,6 +102,7 @@
     std::string getDebugString() const;
     std::string getIsVisibleReason() const;
     bool hasInputInfo() const;
+    FloatRect sourceBounds() const;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 40dffb9..3ed24b2 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -237,12 +237,6 @@
     }
 }
 
-bool getBufferNeedsFiltering(const LayerSnapshot& snapshot, const ui::Size& unrotatedBufferSize) {
-    const int32_t layerWidth = static_cast<int32_t>(snapshot.geomLayerBounds.getWidth());
-    const int32_t layerHeight = static_cast<int32_t>(snapshot.geomLayerBounds.getHeight());
-    return layerWidth != unrotatedBufferSize.width || layerHeight != unrotatedBufferSize.height;
-}
-
 auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
     auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
     if (snapshot.alpha != 1.0f || !snapshot.isContentOpaque()) {
@@ -397,7 +391,9 @@
 
 void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
     ATRACE_NAME("UpdateSnapshots");
-    if (args.forceUpdate || args.displayChanges) {
+    if (args.parentCrop) {
+        mRootSnapshot.geomLayerBounds = *args.parentCrop;
+    } else if (args.forceUpdate || args.displayChanges) {
         mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
     }
     if (args.displayChanges) {
@@ -624,7 +620,8 @@
              RequestedLayerState::Changes::AffectsChildren);
     snapshot.changes = parentChanges | requested.changes;
     snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent ||
-            parentSnapshot.invalidTransform || requested.isHiddenByPolicy();
+            parentSnapshot.invalidTransform || requested.isHiddenByPolicy() ||
+            (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end());
     snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY;
     // TODO(b/238781169) scope down the changes to only buffer updates.
     snapshot.hasReadyFrame =
@@ -667,6 +664,8 @@
         snapshot.sidebandStream = requested.sidebandStream;
         snapshot.transparentRegionHint = requested.transparentRegion;
         snapshot.color.rgb = requested.getColor().rgb;
+        snapshot.currentSdrHdrRatio = requested.currentSdrHdrRatio;
+        snapshot.desiredSdrHdrRatio = requested.desiredSdrHdrRatio;
     }
 
     if (snapshot.isHiddenByPolicyFromParent && !newSnapshot) {
@@ -712,6 +711,10 @@
         snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID
                 ? requested.fixedTransformHint
                 : parentSnapshot.fixedTransformHint;
+        // Display mirrors are always placed in a VirtualDisplay so we never want to capture layers
+        // marked as skip capture
+        snapshot.handleSkipScreenshotFlag = parentSnapshot.handleSkipScreenshotFlag ||
+                (requested.layerStackToMirror != ui::INVALID_LAYER_STACK);
     }
 
     if (forceUpdate || requested.changes.get() != 0) {
@@ -865,11 +868,6 @@
     if (requested.potentialCursor) {
         snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds);
     }
-
-    // TODO(b/238781169) use dest vs src
-    snapshot.bufferNeedsFiltering = snapshot.externalTexture &&
-            getBufferNeedsFiltering(snapshot,
-                                    requested.getUnrotatedBufferSize(displayRotationFlags));
 }
 
 void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot,
@@ -988,6 +986,20 @@
     }
 }
 
+// Visit each visible snapshot in z-order
+void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor,
+                                                  const LayerHierarchy& root) const {
+    root.traverseInZOrder(
+            [this, visitor](const LayerHierarchy&,
+                            const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+                LayerSnapshot* snapshot = getSnapshot(traversalPath);
+                if (snapshot && snapshot->isVisible) {
+                    visitor(*snapshot);
+                }
+                return true;
+            });
+}
+
 void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) {
     for (int i = 0; i < mNumInterestingSnapshots; i++) {
         std::unique_ptr<LayerSnapshot>& snapshot = mSnapshots.at((size_t)i);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index abb7e66..f4544fd 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -36,7 +36,7 @@
 class LayerSnapshotBuilder {
 public:
     struct Args {
-        const LayerHierarchy& root;
+        LayerHierarchy root;
         const LayerLifecycleManager& layerLifecycleManager;
         bool forceUpdate = false;
         bool includeMetadata = false;
@@ -46,6 +46,8 @@
         const renderengine::ShadowSettings& globalShadowSettings;
         bool supportsBlur = true;
         bool forceFullDamage = false;
+        std::optional<FloatRect> parentCrop = std::nullopt;
+        std::unordered_set<uint32_t> excludeLayerIds;
     };
     LayerSnapshotBuilder();
 
@@ -65,6 +67,9 @@
     // Visit each visible snapshot in z-order
     void forEachVisibleSnapshot(const ConstVisitor& visitor) const;
 
+    // Visit each visible snapshot in z-order
+    void forEachVisibleSnapshot(const ConstVisitor& visitor, const LayerHierarchy& root) const;
+
     typedef std::function<void(std::unique_ptr<LayerSnapshot>& snapshot)> Visitor;
     // Visit each visible snapshot in z-order and move the snapshot if needed
     void forEachVisibleSnapshot(const Visitor& visitor);
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 39bf07a..d63b126 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -45,6 +45,16 @@
     return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId);
 }
 
+std::string layerIdsToString(const std::vector<uint32_t>& layerIds) {
+    std::stringstream stream;
+    stream << "{";
+    for (auto layerId : layerIds) {
+        stream << layerId << ",";
+    }
+    stream << "}";
+    return stream.str();
+}
+
 } // namespace
 
 RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args)
@@ -64,8 +74,11 @@
     if (args.parentHandle != nullptr) {
         canBeRoot = false;
     }
-    mirrorId = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
-    if (mirrorId != UNASSIGNED_LAYER_ID) {
+    layerIdToMirror = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
+    if (layerIdToMirror != UNASSIGNED_LAYER_ID) {
+        changes |= RequestedLayerState::Changes::Mirror;
+    } else if (args.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+        layerStackToMirror = args.layerStackToMirror;
         changes |= RequestedLayerState::Changes::Mirror;
     }
 
@@ -96,6 +109,8 @@
     layerStack = ui::DEFAULT_LAYER_STACK;
     transformToDisplayInverse = false;
     dataspace = ui::Dataspace::UNKNOWN;
+    desiredSdrHdrRatio = 1.f;
+    currentSdrHdrRatio = 1.f;
     dataspaceRequested = false;
     hdrMetadata.validTypes = 0;
     surfaceDamageRegion = Region::INVALID_REGION;
@@ -307,7 +322,7 @@
     return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) +
             ",relativeParent=" + layerIdToString(relativeParentId) +
             ",isRelativeOf=" + std::to_string(isRelativeOf) +
-            ",mirrorId=" + layerIdToString(mirrorId) +
+            ",mirrorIds=" + layerIdsToString(mirrorIds) +
             ",handleAlive=" + std::to_string(handleAlive) + ",z=" + std::to_string(z);
 }
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 3a16531..6317b95 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -97,13 +97,15 @@
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate requestedFrameRate;
+    ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK;
+    uint32_t layerIdToMirror = UNASSIGNED_LAYER_ID;
 
     // book keeping states
     bool handleAlive = true;
     bool isRelativeOf = false;
     uint32_t parentId = UNASSIGNED_LAYER_ID;
     uint32_t relativeParentId = UNASSIGNED_LAYER_ID;
-    uint32_t mirrorId = UNASSIGNED_LAYER_ID;
+    std::vector<uint32_t> mirrorIds{};
     uint32_t touchCropId = UNASSIGNED_LAYER_ID;
     uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
     ftl::Flags<RequestedLayerState::Changes> changes;
diff --git a/services/surfaceflinger/FrontEnd/SwapErase.h b/services/surfaceflinger/FrontEnd/SwapErase.h
index f672f99..0061c53 100644
--- a/services/surfaceflinger/FrontEnd/SwapErase.h
+++ b/services/surfaceflinger/FrontEnd/SwapErase.h
@@ -23,12 +23,15 @@
 // remove an element from a vector that avoids relocating all the elements after the one
 // that is erased.
 template <typename T>
-void swapErase(std::vector<T>& vec, const T& value) {
+bool swapErase(std::vector<T>& vec, const T& value) {
+    bool found = false;
     auto it = std::find(vec.begin(), vec.end(), value);
     if (it != vec.end()) {
         std::iter_swap(it, vec.end() - 1);
         vec.erase(vec.end() - 1);
+        found = true;
     }
+    return found;
 }
 
 // Similar to swapErase(std::vector<T>& vec, const T& value) but erases the first element
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index a06b870..7fc825e 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -34,7 +34,7 @@
 class TransactionHandler {
 public:
     struct TransactionFlushState {
-        const TransactionState* transaction;
+        TransactionState* transaction;
         bool firstTransaction = true;
         nsecs_t queueProcessTime = 0;
         // Layer handles that have transactions with buffers that are ready to be applied.
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h
index 4ada2b6..9b70c16 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.h
+++ b/services/surfaceflinger/HdrLayerInfoReporter.h
@@ -33,6 +33,17 @@
         int32_t maxW = 0;
         int32_t maxH = 0;
         int32_t flags = 0;
+        // Counter-intuitively a value of "1" means "as much as you can give me" due to "1" being
+        // the default value for all layers, so any HDR layer with a value of 1.f means no
+        // reduced maximum has been requested
+        // TODO: Should the max desired ratio have a better meaning for HLG/PQ so this can be
+        // eliminated? If we assume an SDR white point of even just 100 nits for those content
+        // then HLG could have a meaningful max ratio of 10.f and PQ of 100.f instead of needing
+        // to treat 1.f as "uncapped"
+        // With peak display brightnesses exceeding 1,000 nits currently, HLG's request could
+        // actually be satisfied in some ambient conditions such that limiting that max for that
+        // content in theory makes sense
+        float maxDesiredSdrHdrRatio = 0.f;
 
         bool operator==(const HdrLayerInfo& other) const {
             return numberOfHdrLayers == other.numberOfHdrLayers && maxW == other.maxW &&
@@ -40,6 +51,20 @@
         }
 
         bool operator!=(const HdrLayerInfo& other) const { return !(*this == other); }
+
+        void mergeDesiredRatio(float update) {
+            if (maxDesiredSdrHdrRatio == 0.f) {
+                // If nothing is set, take the incoming value
+                maxDesiredSdrHdrRatio = update;
+            } else if (update == 1.f) {
+                // If the request is to "go to max", then take it regardless
+                maxDesiredSdrHdrRatio = 1.f;
+            } else if (maxDesiredSdrHdrRatio != 1.f) {
+                // If we're not currently asked to "go to max", then take the max
+                // of the incoming requests
+                maxDesiredSdrHdrRatio = std::max(maxDesiredSdrHdrRatio, update);
+            }
+        }
     };
 
     HdrLayerInfoReporter() = default;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index b0964fe..8c484f0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -146,7 +146,7 @@
         mLayerCreationFlags(args.flags),
         mBorderEnabled(false),
         mTextureName(args.textureName),
-        mLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
+        mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
     ALOGV("Creating Layer %s", getDebugName());
 
     uint32_t layerFlags = 0;
@@ -228,9 +228,7 @@
     if (mBufferInfo.mBuffer != nullptr) {
         callReleaseBufferCallback(mDrawingState.releaseBufferListener,
                                   mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber,
-                                  mBufferInfo.mFence,
-                                  mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
-                                          mOwnerUid));
+                                  mBufferInfo.mFence);
     }
     if (!isClone()) {
         // The original layer and the clone layer share the same texture. Therefore, only one of
@@ -254,7 +252,7 @@
     }
     if (hasTrustedPresentationListener()) {
         mFlinger->mNumTrustedPresentationListeners--;
-        updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/);
+        updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/);
     }
 }
 
@@ -285,7 +283,7 @@
         mRemovedFromDrawingState = true;
         mFlinger->mScheduler->deregisterLayer(this);
     }
-    updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/);
+    updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/);
 
     mFlinger->markLayerPendingRemovalLocked(sp<Layer>::fromExisting(this));
 }
@@ -384,8 +382,9 @@
 }
 
 // No early returns.
-void Layer::updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms,
-                                           bool leaveState) {
+void Layer::updateTrustedPresentationState(const DisplayDevice* display,
+                                           const frontend::LayerSnapshot* snapshot,
+                                           int64_t time_in_ms, bool leaveState) {
     if (!hasTrustedPresentationListener()) {
         return;
     }
@@ -394,12 +393,13 @@
 
     if (!leaveState) {
         const auto outputLayer = findOutputLayerForDisplay(display);
-        if (outputLayer != nullptr) {
+        if (outputLayer != nullptr && snapshot != nullptr) {
             mLastComputedTrustedPresentationState =
-                    computeTrustedPresentationState(mBounds, mSourceBounds,
+                    computeTrustedPresentationState(snapshot->geomLayerBounds,
+                                                    snapshot->sourceBounds(),
                                                     outputLayer->getState().coveredRegion,
-                                                    mScreenBounds, getCompositionState()->alpha,
-                                                    getCompositionState()->geomLayerTransform,
+                                                    snapshot->transformedBounds, snapshot->alpha,
+                                                    snapshot->geomLayerTransform,
                                                     mTrustedPresentationThresholds);
         }
     }
@@ -622,6 +622,8 @@
     snapshot->surfaceDamage = surfaceDamageRegion;
     snapshot->hasProtectedContent = isProtected();
     snapshot->dimmingEnabled = isDimmingEnabled();
+    snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio();
+    snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio();
 
     const bool usesRoundedCorners = hasRoundedCorners();
 
@@ -730,6 +732,40 @@
     return (p != nullptr) ? p->isSecure() : false;
 }
 
+void Layer::transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
+                                      std::vector<JankData>& jankData) {
+    if (mPendingJankClassifications.empty() ||
+        !mPendingJankClassifications.front()->getJankType()) {
+        return;
+    }
+
+    bool includeJankData = false;
+    for (const auto& handle : handles) {
+        for (const auto& cb : handle->callbackIds) {
+            if (cb.includeJankData) {
+                includeJankData = true;
+                break;
+            }
+        }
+
+        if (includeJankData) {
+            jankData.reserve(mPendingJankClassifications.size());
+            break;
+        }
+    }
+
+    while (!mPendingJankClassifications.empty() &&
+           mPendingJankClassifications.front()->getJankType()) {
+        if (includeJankData) {
+            std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
+                    mPendingJankClassifications.front();
+            jankData.emplace_back(
+                    JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
+        }
+        mPendingJankClassifications.pop_front();
+    }
+}
+
 // ----------------------------------------------------------------------------
 // transaction
 // ----------------------------------------------------------------------------
@@ -2694,12 +2730,13 @@
 
 void Layer::callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
                                       const sp<GraphicBuffer>& buffer, uint64_t framenumber,
-                                      const sp<Fence>& releaseFence,
-                                      uint32_t currentMaxAcquiredBufferCount) {
+                                      const sp<Fence>& releaseFence) {
     if (!listener) {
         return;
     }
     ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
+    uint32_t currentMaxAcquiredBufferCount =
+            mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
     listener->onReleaseBuffer({buffer->getId(), framenumber},
                               releaseFence ? releaseFence : Fence::NO_FENCE,
                               currentMaxAcquiredBufferCount);
@@ -2794,16 +2831,7 @@
     }
 
     std::vector<JankData> jankData;
-    jankData.reserve(mPendingJankClassifications.size());
-    while (!mPendingJankClassifications.empty() &&
-           mPendingJankClassifications.front()->getJankType()) {
-        std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
-                mPendingJankClassifications.front();
-        mPendingJankClassifications.pop_front();
-        jankData.emplace_back(
-                JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
-    }
-
+    transferAvailableJankData(mDrawingState.callbackHandles, jankData);
     mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles,
                                                                  jankData);
     mDrawingState.callbackHandles = {};
@@ -2959,9 +2987,7 @@
             // call any release buffer callbacks if set.
             callReleaseBufferCallback(mDrawingState.releaseBufferListener,
                                       mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
-                                      mDrawingState.acquireFence,
-                                      mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
-                                              mOwnerUid));
+                                      mDrawingState.acquireFence);
             decrementPendingBufferCount();
             if (mDrawingState.bufferSurfaceFrameTX != nullptr &&
                 mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) {
@@ -2971,13 +2997,12 @@
         } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
             callReleaseBufferCallback(mDrawingState.releaseBufferListener,
                                       mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
-                                      mLastClientCompositionFence,
-                                      mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
-                                              mOwnerUid));
+                                      mLastClientCompositionFence);
             mLastClientCompositionFence = nullptr;
         }
     }
 
+    mDrawingState.producerId = bufferData.producerId;
     mDrawingState.frameNumber = frameNumber;
     mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
     mDrawingState.buffer = std::move(buffer);
@@ -3041,6 +3066,17 @@
     return true;
 }
 
+bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio) {
+    if (mDrawingState.currentSdrHdrRatio == currentBufferRatio &&
+        mDrawingState.desiredSdrHdrRatio == desiredRatio)
+        return false;
+    mDrawingState.currentSdrHdrRatio = currentBufferRatio;
+    mDrawingState.desiredSdrHdrRatio = desiredRatio;
+    mDrawingState.modified = true;
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) {
     if (mDrawingState.hdrMetadata == hdrMetadata) return false;
     mDrawingState.hdrMetadata = hdrMetadata;
@@ -3083,15 +3119,15 @@
     return true;
 }
 
-bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& handles) {
+bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& handles,
+                                             bool willPresent) {
     // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return
     if (handles.empty()) {
         mReleasePreviousBuffer = false;
         return false;
     }
 
-    const bool willPresent = willPresentCurrentTransaction();
-
+    std::deque<sp<CallbackHandle>> remainingHandles;
     for (const auto& handle : handles) {
         // If this transaction set a buffer on this layer, release its previous buffer
         handle->releasePreviousBuffer = mReleasePreviousBuffer;
@@ -3106,11 +3142,19 @@
             mDrawingState.callbackHandles.push_back(handle);
 
         } else { // If this layer will NOT need to be relatched and presented this frame
-            // Notify the transaction completed thread this handle is done
-            mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle);
+            // Queue this handle to be notified below.
+            remainingHandles.push_back(handle);
         }
     }
 
+    if (!remainingHandles.empty()) {
+        // Notify the transaction completed threads these handles are done. These are only the
+        // handles that were not added to the mDrawingState, which will be notified later.
+        std::vector<JankData> jankData;
+        transferAvailableJankData(remainingHandles, jankData);
+        mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles, jankData);
+    }
+
     mReleasePreviousBuffer = false;
     mCallbackHandleAcquireTimeOrFence = -1;
 
@@ -3165,11 +3209,10 @@
     return fenceSignaled;
 }
 
-bool Layer::onPreComposition(nsecs_t refreshStartTime) {
+void Layer::onPreComposition(nsecs_t refreshStartTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
         handle->refreshStartTime = refreshStartTime;
     }
-    return hasReadyFrame();
 }
 
 void Layer::setAutoRefresh(bool autoRefresh) {
@@ -3272,7 +3315,11 @@
     auto lastDataspace = mBufferInfo.mDataspace;
     mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace);
     if (lastDataspace != mBufferInfo.mDataspace) {
-        mFlinger->mSomeDataspaceChanged = true;
+        mFlinger->mHdrLayerInfoChanged = true;
+    }
+    if (mBufferInfo.mDesiredSdrHdrRatio != mDrawingState.desiredSdrHdrRatio) {
+        mBufferInfo.mDesiredSdrHdrRatio = mDrawingState.desiredSdrHdrRatio;
+        mFlinger->mHdrLayerInfoChanged = true;
     }
     mBufferInfo.mCrop = computeBufferCrop(mDrawingState);
     mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
@@ -3302,39 +3349,6 @@
     return layer;
 }
 
-bool Layer::bufferNeedsFiltering() const {
-    const State& s(getDrawingState());
-    if (!s.buffer) {
-        return false;
-    }
-
-    int32_t bufferWidth = static_cast<int32_t>(s.buffer->getWidth());
-    int32_t bufferHeight = static_cast<int32_t>(s.buffer->getHeight());
-
-    // Undo any transformations on the buffer and return the result.
-    if (s.bufferTransform & ui::Transform::ROT_90) {
-        std::swap(bufferWidth, bufferHeight);
-    }
-
-    if (s.transformToDisplayInverse) {
-        uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
-        if (invTransform & ui::Transform::ROT_90) {
-            std::swap(bufferWidth, bufferHeight);
-        }
-    }
-
-    const Rect layerSize{getBounds()};
-    int32_t layerWidth = layerSize.getWidth();
-    int32_t layerHeight = layerSize.getHeight();
-
-    // Align the layer orientation with the buffer before comparism
-    if (mTransformHint & ui::Transform::ROT_90) {
-        std::swap(layerWidth, layerHeight);
-    }
-
-    return layerWidth != bufferWidth || layerHeight != bufferHeight;
-}
-
 void Layer::decrementPendingBufferCount() {
     int32_t pendingBuffers = --mPendingBufferTransactions;
     tracePendingBufferCount(pendingBuffers);
@@ -3563,6 +3577,14 @@
         }
     }
 
+    if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) {
+        if (mDrawingState.currentSdrHdrRatio != s.currentSdrHdrRatio ||
+            mDrawingState.desiredSdrHdrRatio != s.desiredSdrHdrRatio) {
+            ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__);
+            return false;
+        }
+    }
+
     ALOGV("%s: true", __func__);
     return true;
 }
@@ -3576,7 +3598,7 @@
 
 sp<LayerFE> Layer::getCompositionEngineLayerFE() const {
     // There's no need to get a CE Layer if the layer isn't going to draw anything.
-    return hasSomethingToDraw() ? mLayerFE : nullptr;
+    return hasSomethingToDraw() ? mLegacyLayerFE : nullptr;
 }
 
 const LayerSnapshot* Layer::getLayerSnapshot() const {
@@ -3587,16 +3609,36 @@
     return mSnapshot.get();
 }
 
+std::unique_ptr<frontend::LayerSnapshot> Layer::stealLayerSnapshot() {
+    return std::move(mSnapshot);
+}
+
+void Layer::updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> snapshot) {
+    mSnapshot = std::move(snapshot);
+}
+
 const compositionengine::LayerFECompositionState* Layer::getCompositionState() const {
     return mSnapshot.get();
 }
 
 sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
-    auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName());
+    auto result = mFlinger->getFactory().createLayerFE(mName);
     result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
     return result;
 }
 
+sp<LayerFE> Layer::getCompositionEngineLayerFE(
+        const frontend::LayerHierarchy::TraversalPath& path) {
+    for (auto& [p, layerFE] : mLayerFEs) {
+        if (p == path) {
+            return layerFE;
+        }
+    }
+    auto layerFE = mFlinger->getFactory().createLayerFE(mName);
+    mLayerFEs.emplace_back(path, layerFE);
+    return layerFE;
+}
+
 void Layer::useSurfaceDamage() {
     if (mFlinger->mForceFullDamage) {
         surfaceDamageRegion = Region::INVALID_REGION;
@@ -3796,54 +3838,6 @@
             (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED);
 }
 
-bool Layer::needsFiltering(const DisplayDevice* display) const {
-    if (!hasBufferOrSidebandStream()) {
-        return false;
-    }
-    const auto outputLayer = findOutputLayerForDisplay(display);
-    if (outputLayer == nullptr) {
-        return false;
-    }
-
-    // We need filtering if the sourceCrop rectangle size does not match the
-    // displayframe rectangle size (not a 1:1 render)
-    const auto& compositionState = outputLayer->getState();
-    const auto displayFrame = compositionState.displayFrame;
-    const auto sourceCrop = compositionState.sourceCrop;
-    return sourceCrop.getHeight() != displayFrame.getHeight() ||
-            sourceCrop.getWidth() != displayFrame.getWidth();
-}
-
-bool Layer::needsFilteringForScreenshots(const DisplayDevice* display,
-                                         const ui::Transform& inverseParentTransform) const {
-    if (!hasBufferOrSidebandStream()) {
-        return false;
-    }
-    const auto outputLayer = findOutputLayerForDisplay(display);
-    if (outputLayer == nullptr) {
-        return false;
-    }
-
-    // We need filtering if the sourceCrop rectangle size does not match the
-    // viewport rectangle size (not a 1:1 render)
-    const auto& compositionState = outputLayer->getState();
-    const ui::Transform& displayTransform = display->getTransform();
-    const ui::Transform inverseTransform = inverseParentTransform * displayTransform.inverse();
-    // Undo the transformation of the displayFrame so that we're back into
-    // layer-stack space.
-    const Rect frame = inverseTransform.transform(compositionState.displayFrame);
-    const FloatRect sourceCrop = compositionState.sourceCrop;
-
-    int32_t frameHeight = frame.getHeight();
-    int32_t frameWidth = frame.getWidth();
-    // If the display transform had a rotational component then undo the
-    // rotation so that the orientation matches the source crop.
-    if (displayTransform.getOrientation() & ui::Transform::ROT_90) {
-        std::swap(frameHeight, frameWidth);
-    }
-    return sourceCrop.getHeight() != frameHeight || sourceCrop.getWidth() != frameWidth;
-}
-
 void Layer::latchAndReleaseBuffer() {
     if (hasReadyFrame()) {
         bool ignored = false;
@@ -3989,7 +3983,6 @@
     snapshot->layerOpaqueFlagSet =
             (mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
     snapshot->isHdrY410 = isHdrY410();
-    snapshot->bufferNeedsFiltering = bufferNeedsFiltering();
     sp<Layer> p = mDrawingParent.promote();
     if (p != nullptr) {
         snapshot->parentTransform = p->getTransform();
@@ -4041,28 +4034,6 @@
     }
 }
 
-LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) {
-    if (mLayer) {
-        mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
-    }
-}
-
-LayerSnapshotGuard::~LayerSnapshotGuard() {
-    if (mLayer) {
-        mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot);
-    }
-}
-
-LayerSnapshotGuard::LayerSnapshotGuard(LayerSnapshotGuard&& other) : mLayer(other.mLayer) {
-    other.mLayer = nullptr;
-}
-
-LayerSnapshotGuard& LayerSnapshotGuard::operator=(LayerSnapshotGuard&& other) {
-    mLayer = other.mLayer;
-    other.mLayer = nullptr;
-    return *this;
-}
-
 void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
                                        TrustedPresentationListener const& listener) {
     bool hadTrustedPresentationListener = hasTrustedPresentationListener();
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7631f5d..bf8cc7e 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -141,6 +141,8 @@
 
         uint64_t frameNumber;
         ui::Transform transform;
+
+        uint32_t producerId = 0;
         uint32_t bufferTransform;
         bool transformToDisplayInverse;
         Region transparentRegionHint;
@@ -223,6 +225,8 @@
         gui::DropInputMode dropInputMode;
         bool autoRefresh = false;
         bool dimmingEnabled = true;
+        float currentSdrHdrRatio = 1.f;
+        float desiredSdrHdrRatio = 1.f;
     };
 
     explicit Layer(const LayerCreationArgs& args);
@@ -289,7 +293,9 @@
     virtual mat4 getColorTransform() const;
     virtual bool hasColorTransform() const;
     virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; }
-    virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; };
+    virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }
+    float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; }
+    float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; }
 
     bool setTransform(uint32_t /*transform*/);
     bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/);
@@ -298,11 +304,13 @@
                    nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
                    std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
     bool setDataspace(ui::Dataspace /*dataspace*/);
+    bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio);
     bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
     bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
     bool setApi(int32_t /*api*/);
     bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/);
-    bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/);
+    bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/,
+                                          bool willPresent);
     virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace);
     virtual bool setColorSpaceAgnostic(const bool agnostic);
     virtual bool setDimmingEnabled(const bool dimmingEnabled);
@@ -323,9 +331,12 @@
 
     virtual sp<LayerFE> getCompositionEngineLayerFE() const;
     virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
+    sp<LayerFE> getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
 
     const frontend::LayerSnapshot* getLayerSnapshot() const;
     frontend::LayerSnapshot* editLayerSnapshot();
+    std::unique_ptr<frontend::LayerSnapshot> stealLayerSnapshot();
+    void updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> snapshot);
 
     // If we have received a new buffer this frame, we will pass its surface
     // damage down to hardware composer. Otherwise, we must send a region with
@@ -499,6 +510,7 @@
         uint64_t mFrameNumber;
 
         bool mFrameLatencyNeeded{false};
+        float mDesiredSdrHdrRatio = 1.f;
     };
 
     BufferInfo mBufferInfo;
@@ -506,7 +518,7 @@
     // implements compositionengine::LayerFE
     const compositionengine::LayerFECompositionState* getCompositionState() const;
     bool fenceHasSignaled() const;
-    bool onPreComposition(nsecs_t refreshStartTime);
+    void onPreComposition(nsecs_t refreshStartTime);
     void onLayerDisplayed(ftl::SharedFuture<FenceResult>);
 
     void setWasClientComposed(const sp<Fence>& fence) {
@@ -533,7 +545,8 @@
                                                 const FloatRect& screenBounds, float,
                                                 const ui::Transform&,
                                                 const TrustedPresentationThresholds&);
-    void updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms,
+    void updateTrustedPresentationState(const DisplayDevice* display,
+                                        const frontend::LayerSnapshot* snapshot, int64_t time_in_ms,
                                         bool leaveState);
 
     inline bool hasTrustedPresentationListener() {
@@ -825,6 +838,11 @@
     void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
     void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
                                         std::unordered_set<Layer*>& visited);
+    bool willPresentCurrentTransaction() const;
+
+    void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
+                                   const sp<GraphicBuffer>& buffer, uint64_t framenumber,
+                                   const sp<Fence>& releaseFence);
 
 protected:
     // For unit tests
@@ -1030,17 +1048,15 @@
     // Crop that applies to the buffer
     Rect computeBufferCrop(const State& s);
 
-    bool willPresentCurrentTransaction() const;
-
-    // Returns true if the transformed buffer size does not match the layer size and we need
-    // to apply filtering.
-    bool bufferNeedsFiltering() const;
-
     void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
                                    const sp<GraphicBuffer>& buffer, uint64_t framenumber,
                                    const sp<Fence>& releaseFence,
                                    uint32_t currentMaxAcquiredBufferCount);
 
+    // Returns true if the transformed buffer size does not match the layer size and we need
+    // to apply filtering.
+    bool bufferNeedsFiltering() const;
+
     // Returns true if there is a valid color to fill.
     bool fillsColor() const;
     // Returns true if this layer has a blur value.
@@ -1058,6 +1074,11 @@
 
     void updateChildrenSnapshots(bool updateGeometry);
 
+    // Fills the provided vector with the currently available JankData and removes the processed
+    // JankData from the pending list.
+    void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
+                                   std::vector<JankData>& jankData);
+
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling, which is
     // a transform from the current layer coordinate space to display(screen) coordinate space.
@@ -1143,34 +1164,10 @@
     // not specify a destination frame.
     ui::Transform mRequestedTransform;
 
-    sp<LayerFE> mLayerFE;
+    sp<LayerFE> mLegacyLayerFE;
+    std::vector<std::pair<frontend::LayerHierarchy::TraversalPath, sp<LayerFE>>> mLayerFEs;
     std::unique_ptr<frontend::LayerSnapshot> mSnapshot =
             std::make_unique<frontend::LayerSnapshot>();
-
-    friend class LayerSnapshotGuard;
-};
-
-// LayerSnapshotGuard manages the movement of LayerSnapshot between a Layer and its corresponding
-// LayerFE. This class must be used whenever LayerFEs are passed to CompositionEngine. Instances of
-// LayerSnapshotGuard should only be constructed on the main thread and should not be moved outside
-// the main thread.
-//
-// Moving the snapshot instead of sharing common state prevents use of LayerFE outside the main
-// thread by making errors obvious (i.e. use outside the main thread results in SEGFAULTs due to
-// nullptr dereference).
-class LayerSnapshotGuard {
-public:
-    LayerSnapshotGuard(Layer* layer) REQUIRES(kMainThreadContext);
-    ~LayerSnapshotGuard() REQUIRES(kMainThreadContext);
-
-    LayerSnapshotGuard(const LayerSnapshotGuard&) = delete;
-    LayerSnapshotGuard& operator=(const LayerSnapshotGuard&) = delete;
-
-    LayerSnapshotGuard(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext);
-    LayerSnapshotGuard& operator=(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext);
-
-private:
-    Layer* mLayer;
 };
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate);
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index c31a2e3..b9c8b78 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -249,14 +249,11 @@
     layerSettings.frameNumber = mSnapshot->frameNumber;
     layerSettings.bufferId = mSnapshot->externalTexture->getId();
 
-    const bool useFiltering = targetSettings.needsFiltering ||
-            mSnapshot->geomLayerTransform.needsBilinearFiltering() ||
-            mSnapshot->bufferNeedsFiltering;
-
     // Query the texture matrix given our current filtering mode.
     float textureMatrix[16];
     getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop,
-                              mSnapshot->geomBufferTransform, useFiltering, textureMatrix);
+                              mSnapshot->geomBufferTransform, targetSettings.needsFiltering,
+                              textureMatrix);
 
     if (mSnapshot->geomBufferUsesDisplayInverseTransform) {
         /*
@@ -306,7 +303,7 @@
             mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) *
             mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f));
 
-    layerSettings.source.buffer.useTextureFiltering = useFiltering;
+    layerSettings.source.buffer.useTextureFiltering = targetSettings.needsFiltering;
     layerSettings.source.buffer.textureTransform =
             mat4(static_cast<const float*>(textureMatrix)) * tr;
 
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 554fae4..03a7f22 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -39,8 +39,8 @@
 
 LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop,
                                  ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly,
-                                 const Rect& layerStackRect, bool allowSecureLayers)
-      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, layerStackRect, allowSecureLayers),
+                                 bool allowSecureLayers)
+      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, allowSecureLayers),
         mLayer(std::move(layer)),
         mCrop(crop),
         mFlinger(flinger),
@@ -50,33 +50,17 @@
     return mTransform;
 }
 
-Rect LayerRenderArea::getBounds() const {
-    return mLayer->getBufferSize(mLayer->getDrawingState());
-}
-
-int LayerRenderArea::getHeight() const {
-    return mLayer->getBufferSize(mLayer->getDrawingState()).getHeight();
-}
-
-int LayerRenderArea::getWidth() const {
-    return mLayer->getBufferSize(mLayer->getDrawingState()).getWidth();
-}
-
 bool LayerRenderArea::isSecure() const {
     return mAllowSecureLayers;
 }
 
-bool LayerRenderArea::needsFiltering() const {
-    return mNeedsFiltering;
-}
-
 sp<const DisplayDevice> LayerRenderArea::getDisplayDevice() const {
     return nullptr;
 }
 
 Rect LayerRenderArea::getSourceCrop() const {
     if (mCrop.isEmpty()) {
-        return getBounds();
+        return mLayer->getBufferSize(mLayer->getDrawingState());
     } else {
         return mCrop;
     }
@@ -85,10 +69,14 @@
 void LayerRenderArea::render(std::function<void()> drawLayers) {
     using namespace std::string_literals;
 
-    const Rect sourceCrop = getSourceCrop();
-    // no need to check rotation because there is none
-    mNeedsFiltering = sourceCrop.width() != getReqWidth() || sourceCrop.height() != getReqHeight();
+    if (!mChildrenOnly) {
+        mTransform = mLayer->getTransform().inverse();
+    }
 
+    if (mFlinger.mLayerLifecycleManagerEnabled) {
+        drawLayers();
+        return;
+    }
     // If layer is offscreen, update mirroring info if it exists
     if (mLayer->isRemovedFromCurrentState()) {
         mLayer->traverse(LayerVector::StateSet::Drawing,
@@ -98,7 +86,6 @@
     }
 
     if (!mChildrenOnly) {
-        mTransform = mLayer->getTransform().inverse();
         // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen
         // layers in a regular cycles.
         if (mLayer->isRemovedFromCurrentState()) {
@@ -116,7 +103,7 @@
                  LayerMetadata()});
         {
             Mutex::Autolock _l(mFlinger.mStateLock);
-            reparentForDrawing(mLayer, screenshotParentLayer, sourceCrop);
+            reparentForDrawing(mLayer, screenshotParentLayer, getSourceCrop());
         }
         drawLayers();
         {
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index 41273e0..322dbd1 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -33,15 +33,10 @@
 class LayerRenderArea : public RenderArea {
 public:
     LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop, ui::Size reqSize,
-                    ui::Dataspace reqDataSpace, bool childrenOnly, const Rect& layerStackRect,
-                    bool allowSecureLayers);
+                    ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers);
 
     const ui::Transform& getTransform() const override;
-    Rect getBounds() const override;
-    int getHeight() const override;
-    int getWidth() const override;
     bool isSecure() const override;
-    bool needsFiltering() const override;
     sp<const DisplayDevice> getDisplayDevice() const override;
     Rect getSourceCrop() const override;
 
@@ -53,7 +48,6 @@
     const Rect mCrop;
 
     ui::Transform mTransform;
-    bool mNeedsFiltering = false;
 
     SurfaceFlinger& mFlinger;
     const bool mChildrenOnly;
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 6e64e0a..839500f 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -344,12 +344,13 @@
                                                      renderengine::impl::ExternalTexture::Usage::
                                                              WRITEABLE);
     }
+    auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
 
     constexpr bool kRegionSampling = true;
     constexpr bool kGrayscale = false;
 
     if (const auto fenceResult =
-                mFlinger.captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer,
+                mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer,
                                              kRegionSampling, kGrayscale, nullptr)
                         .get();
         fenceResult.ok()) {
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 387364c..910fce0 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -25,14 +25,27 @@
     static float getCaptureFillValue(CaptureFill captureFill);
 
     RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace,
-               const Rect& layerStackRect, bool allowSecureLayers = false,
-               RotationFlags rotation = ui::Transform::ROT_0)
+               bool allowSecureLayers = false, RotationFlags rotation = ui::Transform::ROT_0)
           : mAllowSecureLayers(allowSecureLayers),
             mReqSize(reqSize),
             mReqDataSpace(reqDataSpace),
             mCaptureFill(captureFill),
-            mRotationFlags(rotation),
-            mLayerStackSpaceRect(layerStackRect) {}
+            mRotationFlags(rotation) {}
+
+    static std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> fromTraverseLayersLambda(
+            std::function<void(const LayerVector::Visitor&)> traverseLayers) {
+        return [traverseLayers = std::move(traverseLayers)]() {
+            std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+            traverseLayers([&](Layer* layer) {
+                // Layer::prepareClientComposition uses the layer's snapshot to populate the
+                // resulting LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings
+                // are generated with the layer's current buffer and geometry.
+                layer->updateSnapshot(true /* updateGeometry */);
+                layers.emplace_back(layer, layer->copyCompositionEngineLayerFE());
+            });
+            return layers;
+        };
+    }
 
     virtual ~RenderArea() = default;
 
@@ -43,20 +56,10 @@
     // blacked out / skipped when rendered to an insecure render area.
     virtual bool isSecure() const = 0;
 
-    // Returns true if the otherwise disabled layer filtering should be
-    // enabled when rendering to this render area.
-    virtual bool needsFiltering() const = 0;
-
     // Returns the transform to be applied on layers to transform them into
     // the logical render area.
     virtual const ui::Transform& getTransform() const = 0;
 
-    // Returns the size of the logical render area.  Layers are clipped to the
-    // logical render area.
-    virtual int getWidth() const = 0;
-    virtual int getHeight() const = 0;
-    virtual Rect getBounds() const = 0;
-
     // Returns the source crop of the render area.  The source crop defines
     // how layers are projected from the logical render area onto the physical
     // render area.  It can be larger than the logical render area.  It can
@@ -83,9 +86,6 @@
 
     virtual sp<const DisplayDevice> getDisplayDevice() const = 0;
 
-    // Returns the source display viewport.
-    const Rect& getLayerStackSpaceRect() const { return mLayerStackSpaceRect; }
-
     // If this is a LayerRenderArea, return the root layer of the
     // capture operation.
     virtual sp<Layer> getParentLayer() const { return nullptr; }
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index a902a8e..eb6d7e4 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -541,6 +541,13 @@
 bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event,
                                      const sp<EventThreadConnection>& connection) const {
     const auto throttleVsync = [&] {
+        const auto& vsyncData = event.vsync.vsyncData;
+        if (connection->frameRate.isValid()) {
+            return !mVsyncSchedule.getTracker()
+                            .isVSyncInPhase(vsyncData.preferredExpectedPresentationTime(),
+                                            connection->frameRate);
+        }
+
         return mThrottleVsyncCallback &&
                 mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(),
                                        connection->mOwnerUid);
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index ab9085e..347dc4a 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -87,6 +87,9 @@
     const uid_t mOwnerUid;
     const EventRegistrationFlags mEventRegistration;
 
+    /** The frame rate set to the attached choreographer. */
+    Fps frameRate;
+
 private:
     virtual void onFirstRef();
     EventThread* const mEventThread;
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
new file mode 100644
index 0000000..c4de749
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -0,0 +1,35 @@
+/*
+ * 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 <vector>
+
+#include "Display/DisplayModeRequest.h"
+
+namespace android::scheduler {
+
+struct ISchedulerCallback {
+    virtual void setVsyncEnabled(bool) = 0;
+    virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
+    virtual void kernelTimerChanged(bool expired) = 0;
+    virtual void triggerOnFrameRateOverridesChanged() = 0;
+
+protected:
+    ~ISchedulerCallback() = default;
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 55fa402..e853833 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -32,6 +32,7 @@
 #include <utility>
 
 #include "../Layer.h"
+#include "EventThread.h"
 #include "LayerInfo.h"
 
 namespace android::scheduler {
@@ -140,6 +141,22 @@
 
     info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps);
 
+    // Set frame rate to attached choreographer.
+    // TODO(b/260898223): Change to use layer hierarchy and handle frame rate vote.
+    if (updateType == LayerUpdateType::SetFrameRate) {
+        auto range = mAttachedChoreographers.equal_range(id);
+        auto it = range.first;
+        while (it != range.second) {
+            sp<EventThreadConnection> choreographerConnection = it->second.promote();
+            if (choreographerConnection) {
+                choreographerConnection->frameRate = layer->getFrameRateForLayerTree().rate;
+                it++;
+            } else {
+                it = mAttachedChoreographers.erase(it);
+            }
+        }
+    }
+
     // Activate layer if inactive.
     if (found == LayerStatus::LayerInInactiveMap) {
         mActiveLayerInfos.insert(
@@ -294,6 +311,12 @@
     return 0.f;
 }
 
+void LayerHistory::attachChoreographer(int32_t layerId,
+                                       const sp<EventThreadConnection>& choreographerConnection) {
+    std::lock_guard lock(mLock);
+    mAttachedChoreographers.insert({layerId, wp<EventThreadConnection>(choreographerConnection)});
+}
+
 auto LayerHistory::findLayer(int32_t id) -> std::pair<LayerStatus, LayerPair*> {
     // the layer could be in either the active or inactive map, try both
     auto it = mActiveLayerInfos.find(id);
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 5022906..68e7030 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -27,6 +27,8 @@
 #include <utility>
 #include <vector>
 
+#include "EventThread.h"
+
 #include "RefreshRateSelector.h"
 
 namespace android {
@@ -80,6 +82,9 @@
     // return the frames per second of the layer with the given sequence id.
     float getLayerFramerate(nsecs_t now, int32_t id) const;
 
+    void attachChoreographer(int32_t layerId,
+                             const sp<EventThreadConnection>& choreographerConnection);
+
 private:
     friend class LayerHistoryTest;
     friend class TestableScheduler;
@@ -117,6 +122,10 @@
     LayerInfos mActiveLayerInfos GUARDED_BY(mLock);
     LayerInfos mInactiveLayerInfos GUARDED_BY(mLock);
 
+    // Map keyed by layer ID (sequence) to choreographer connections.
+    std::unordered_multimap<int32_t, wp<EventThreadConnection>> mAttachedChoreographers
+            GUARDED_BY(mLock);
+
     uint32_t mDisplayArea = 0;
 
     // Whether to emit systrace output and debug logs.
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 9b04497..dec8f59 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -17,12 +17,12 @@
 #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>
 
-#include <gui/DisplayEventReceiver.h>
+#include <scheduler/interface/ICompositor.h>
 
 #include "EventThread.h"
 #include "FrameTimeline.h"
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index ad0ea72..0d59337 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -37,15 +37,7 @@
 
 namespace android {
 
-struct ICompositor {
-    virtual void configure() = 0;
-    virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0;
-    virtual void composite(TimePoint frameTime, VsyncId) = 0;
-    virtual void sample() = 0;
-
-protected:
-    ~ICompositor() = default;
-};
+struct ICompositor;
 
 template <typename F>
 class Task : public MessageHandler {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 30821d8..f6fe468 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -238,7 +238,6 @@
         std::string name = to_string(frameRateMode);
 
         ALOGV("%s sorting scores %.2f", name.c_str(), overallScore);
-        ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
 
         if (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) {
             return overallScore > rhs.overallScore;
@@ -929,7 +928,28 @@
                                          RefreshRateOrder refreshRateOrder,
                                          std::optional<DisplayModeId> preferredDisplayModeOpt) const
         -> FrameRateRanking {
+    using fps_approx_ops::operator<;
     const char* const whence = __func__;
+
+    // find the highest frame rate for each display mode
+    ftl::SmallMap<DisplayModeId, Fps, 8> maxRenderRateForMode;
+    const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
+    if (ascending) {
+        // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually
+        //  use a lower frame rate when we want Ascending frame rates.
+        for (const auto& frameRateMode : mPrimaryFrameRates) {
+            if (anchorGroupOpt && frameRateMode.modePtr->getGroup() != anchorGroupOpt) {
+                continue;
+            }
+
+            const auto [iter, _] = maxRenderRateForMode.try_emplace(frameRateMode.modePtr->getId(),
+                                                                    frameRateMode.fps);
+            if (iter->second < frameRateMode.fps) {
+                iter->second = frameRateMode.fps;
+            }
+        }
+    }
+
     std::deque<ScoredFrameRate> ranking;
     const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
         const auto& modePtr = frameRateMode.modePtr;
@@ -937,9 +957,17 @@
             return;
         }
 
+        const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
+        const auto id = frameRateMode.modePtr->getId();
+        if (ascending && frameRateMode.fps < *maxRenderRateForMode.get(id)) {
+            // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually
+            //  use a lower frame rate when we want Ascending frame rates.
+            return;
+        }
+
         float score = calculateDistanceScoreFromMax(frameRateMode.fps);
-        const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending);
-        if (inverseScore) {
+
+        if (ascending) {
             score = 1.0f / score;
         }
         if (preferredDisplayModeOpt) {
@@ -951,6 +979,7 @@
             constexpr float kNonPreferredModePenalty = 0.95f;
             score *= kNonPreferredModePenalty;
         }
+
         ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
               to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score);
         ranking.emplace_back(ScoredFrameRate{frameRateMode, score});
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 1fc1519..17cdff9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -28,12 +28,14 @@
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <ftl/small_map.h>
+#include <gui/TraceUtils.h>
 #include <gui/WindowInfo.h>
 #include <system/window.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 
 #include <FrameTimeline/FrameTimeline.h>
+#include <scheduler/interface/ICompositor.h>
+
 #include <algorithm>
 #include <cinttypes>
 #include <cstdint>
@@ -45,6 +47,7 @@
 #include "Display/DisplayMap.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
+#include "FrontEnd/LayerHandle.h"
 #include "OneShotTimer.h"
 #include "SurfaceFlingerProperties.h"
 #include "VSyncPredictor.h"
@@ -125,6 +128,11 @@
     std::scoped_lock lock(mDisplayLock);
     mRefreshRateSelectors.erase(displayId);
 
+    // Do not allow removing the final display. Code in the scheduler expects
+    // there to be at least one display. (This may be relaxed in the future with
+    // headless virtual display.)
+    LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!");
+
     promoteLeaderDisplay();
 }
 
@@ -147,7 +155,7 @@
 }
 
 void Scheduler::createVsyncSchedule(FeatureFlags features) {
-    mVsyncSchedule.emplace(features);
+    mVsyncSchedule = std::make_unique<VsyncSchedule>(features);
 }
 
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
@@ -163,9 +171,14 @@
         return true;
     }
 
+    ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
     return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate);
 }
 
+bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const {
+    return mVsyncSchedule->getTracker().isVSyncInPhase(timePoint.ns(), frameRate);
+}
+
 impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
     return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
         return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid);
@@ -217,15 +230,21 @@
 }
 
 sp<EventThreadConnection> Scheduler::createConnectionInternal(
-        EventThread* eventThread, EventRegistrationFlags eventRegistration) {
-    return eventThread->createEventConnection([&] { resync(); }, eventRegistration);
+        EventThread* eventThread, EventRegistrationFlags eventRegistration,
+        const sp<IBinder>& layerHandle) {
+    int32_t layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
+    auto connection = eventThread->createEventConnection([&] { resync(); }, eventRegistration);
+    mLayerHistory.attachChoreographer(layerId, connection);
+    return connection;
 }
 
 sp<IDisplayEventConnection> Scheduler::createDisplayEventConnection(
-        ConnectionHandle handle, EventRegistrationFlags eventRegistration) {
+        ConnectionHandle handle, EventRegistrationFlags eventRegistration,
+        const sp<IBinder>& layerHandle) {
     std::lock_guard<std::mutex> lock(mConnectionsLock);
     RETURN_IF_INVALID_HANDLE(handle, nullptr);
-    return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration);
+    return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration,
+                                    layerHandle);
 }
 
 sp<EventThreadConnection> Scheduler::getEventConnection(ConnectionHandle handle) {
@@ -254,7 +273,6 @@
         thread = mConnections[handle].thread.get();
     }
     thread->onScreenAcquired();
-    mScreenAcquired = true;
 }
 
 void Scheduler::onScreenReleased(ConnectionHandle handle) {
@@ -265,7 +283,6 @@
         thread = mConnections[handle].thread.get();
     }
     thread->onScreenReleased();
-    mScreenAcquired = false;
 }
 
 void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
@@ -377,38 +394,17 @@
 }
 
 void Scheduler::enableHardwareVsync() {
-    std::lock_guard<std::mutex> lock(mHWVsyncLock);
-    if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) {
-        mVsyncSchedule->getTracker().resetModel();
-        mSchedulerCallback.setVsyncEnabled(true);
-        mPrimaryHWVsyncEnabled = true;
-    }
+    mVsyncSchedule->enableHardwareVsync(mSchedulerCallback);
 }
 
-void Scheduler::disableHardwareVsync(bool makeUnavailable) {
-    std::lock_guard<std::mutex> lock(mHWVsyncLock);
-    if (mPrimaryHWVsyncEnabled) {
-        mSchedulerCallback.setVsyncEnabled(false);
-        mPrimaryHWVsyncEnabled = false;
-    }
-    if (makeUnavailable) {
-        mHWVsyncAvailable = false;
-    }
+void Scheduler::disableHardwareVsync(bool disallow) {
+    mVsyncSchedule->disableHardwareVsync(mSchedulerCallback, disallow);
 }
 
-void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) {
-    {
-        std::lock_guard<std::mutex> lock(mHWVsyncLock);
-        if (makeAvailable) {
-            mHWVsyncAvailable = makeAvailable;
-        } else if (!mHWVsyncAvailable) {
-            // Hardware vsync is not currently available, so abort the resync
-            // attempt for now
-            return;
-        }
+void Scheduler::resyncToHardwareVsync(bool allowToEnable, Fps refreshRate) {
+    if (mVsyncSchedule->isHardwareVsyncAllowed(allowToEnable) && refreshRate.isValid()) {
+        mVsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod());
     }
-
-    setVsyncPeriod(refreshRate.getPeriodNsecs());
 }
 
 void Scheduler::setRenderRate(Fps renderFrameRate) {
@@ -441,37 +437,12 @@
     }
 }
 
-void Scheduler::setVsyncPeriod(nsecs_t period) {
-    if (period <= 0) return;
-
-    std::lock_guard<std::mutex> lock(mHWVsyncLock);
-    mVsyncSchedule->getController().startPeriodTransition(period);
-
-    if (!mPrimaryHWVsyncEnabled) {
-        mVsyncSchedule->getTracker().resetModel();
-        mSchedulerCallback.setVsyncEnabled(true);
-        mPrimaryHWVsyncEnabled = true;
-    }
-}
-
-void Scheduler::addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
-                                bool* periodFlushed) {
-    bool needsHwVsync = false;
-    *periodFlushed = false;
-    { // Scope for the lock
-        std::lock_guard<std::mutex> lock(mHWVsyncLock);
-        if (mPrimaryHWVsyncEnabled) {
-            needsHwVsync =
-                    mVsyncSchedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod,
-                                                                        periodFlushed);
-        }
-    }
-
-    if (needsHwVsync) {
-        enableHardwareVsync();
-    } else {
-        disableHardwareVsync(false);
-    }
+bool Scheduler::addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriodIn) {
+    const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) {
+        return Period::fromNs(nanos);
+    });
+    return mVsyncSchedule->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp),
+                                           hwcVsyncPeriod);
 }
 
 void Scheduler::addPresentFence(std::shared_ptr<FenceTime> fence) {
@@ -619,15 +590,6 @@
 
     mFrameRateOverrideMappings.dump(dumper);
     dumper.eol();
-
-    {
-        utils::Dumper::Section section(dumper, "Hardware VSYNC"sv);
-
-        std::lock_guard lock(mHWVsyncLock);
-        dumper.dump("screenAcquired"sv, mScreenAcquired.load());
-        dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable);
-        dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled);
-    }
 }
 
 void Scheduler::dumpVsync(std::string& out) const {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ef7d0cf..a340919 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -43,6 +43,7 @@
 #include "Display/DisplayModeRequest.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
+#include "ISchedulerCallback.h"
 #include "LayerHistory.h"
 #include "MessageQueue.h"
 #include "OneShotTimer.h"
@@ -92,16 +93,6 @@
 
 using GlobalSignals = RefreshRateSelector::GlobalSignals;
 
-struct ISchedulerCallback {
-    virtual void setVsyncEnabled(bool) = 0;
-    virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
-    virtual void kernelTimerChanged(bool expired) = 0;
-    virtual void triggerOnFrameRateOverridesChanged() = 0;
-
-protected:
-    ~ISchedulerCallback() = default;
-};
-
 class Scheduler : android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
@@ -158,7 +149,8 @@
                                        std::chrono::nanoseconds readyDuration);
 
     sp<IDisplayEventConnection> createDisplayEventConnection(
-            ConnectionHandle, EventRegistrationFlags eventRegistration = {});
+            ConnectionHandle, EventRegistrationFlags eventRegistration = {},
+            const sp<IBinder>& layerHandle = nullptr);
 
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
@@ -191,20 +183,20 @@
     void setRenderRate(Fps);
 
     void enableHardwareVsync();
-    void disableHardwareVsync(bool makeUnavailable);
+    void disableHardwareVsync(bool disallow);
 
     // Resyncs the scheduler to hardware vsync.
-    // If makeAvailable is true, then hardware vsync will be turned on.
+    // If allowToEnable is true, then hardware vsync will be turned on.
     // Otherwise, if hardware vsync is not already enabled then this method will
     // no-op.
-    void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate);
+    void resyncToHardwareVsync(bool allowToEnable, Fps refreshRate);
     void resync() EXCLUDES(mDisplayLock);
     void forceNextResync() { mLastResyncTime = 0; }
 
-    // Passes a vsync sample to VsyncController. periodFlushed will be true if
-    // VsyncController detected that the vsync period changed, and false otherwise.
-    void addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
-                         bool* periodFlushed);
+    // Passes a vsync sample to VsyncController. Returns true if
+    // VsyncController detected that the vsync period changed and false
+    // otherwise.
+    bool addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod);
     void addPresentFence(std::shared_ptr<FenceTime>);
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
@@ -231,6 +223,9 @@
     // for a given uid
     bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const;
 
+    // Checks if a vsync timestamp is in phase for a frame rate
+    bool isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const;
+
     void dump(utils::Dumper&) const;
     void dump(ConnectionHandle, std::string&) const;
     void dumpVsync(std::string&) const;
@@ -262,6 +257,10 @@
         return leaderSelectorPtr()->getActiveMode().fps.getPeriod();
     }
 
+    Fps getLeaderRefreshRate() const EXCLUDES(mDisplayLock) {
+        return leaderSelectorPtr()->getActiveMode().fps;
+    }
+
     // Returns the framerate of the layer with the given sequence ID
     float getLayerFramerate(nsecs_t now, int32_t id) const {
         return mLayerHistory.getLayerFramerate(now, id);
@@ -280,7 +279,8 @@
     // Create a connection on the given EventThread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread>);
     sp<EventThreadConnection> createConnectionInternal(
-            EventThread*, EventRegistrationFlags eventRegistration = {});
+            EventThread*, EventRegistrationFlags eventRegistration = {},
+            const sp<IBinder>& layerHandle = nullptr);
 
     // Update feature state machine to given state when corresponding timer resets or expires.
     void kernelIdleTimerCallback(TimerState) EXCLUDES(mDisplayLock);
@@ -288,7 +288,6 @@
     void touchTimerCallback(TimerState);
     void displayPowerTimerCallback(TimerState);
 
-    void setVsyncPeriod(nsecs_t period);
     void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod);
 
     // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new
@@ -353,14 +352,10 @@
     ConnectionHandle mAppConnectionHandle;
     ConnectionHandle mSfConnectionHandle;
 
-    mutable std::mutex mHWVsyncLock;
-    bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false;
-    bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false;
-
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
     const FeatureFlags mFeatures;
-    std::optional<VsyncSchedule> mVsyncSchedule;
+    std::unique_ptr<VsyncSchedule> mVsyncSchedule;
 
     // Shifts the VSYNC phase during certain transactions and refresh rate changes.
     const sp<VsyncModulator> mVsyncModulator;
@@ -430,9 +425,6 @@
     static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
 
     FrameRateOverrideMappings mFrameRateOverrideMappings;
-
-    // Keeps track of whether the screen is acquired for debug
-    std::atomic<bool> mScreenAcquired = false;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 02e12fd..f8cb323 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -31,8 +31,8 @@
 #include <android-base/stringprintf.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
+#include <gui/TraceUtils.h>
 #include <utils/Log.h>
-#include <utils/Trace.h>
 
 #include "RefreshRateSelector.h"
 #include "VSyncPredictor.h"
@@ -282,6 +282,13 @@
 }
 
 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);
+
     struct VsyncError {
         nsecs_t vsyncTimestamp;
         float error;
@@ -304,6 +311,7 @@
     if (knownTimestampIter == mRateDivisorKnownTimestampMap.end()) {
         const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint);
         mRateDivisorKnownTimestampMap[dividedPeriod] = vsync;
+        ATRACE_FORMAT_INSTANT("(first) knownVsync in: %.2f", getTimePointIn(now, vsync));
         return true;
     }
 
@@ -323,6 +331,8 @@
 
     const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end());
     mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp;
+    ATRACE_FORMAT_INSTANT("knownVsync in: %.2f",
+                          getTimePointIn(now, minVsyncError->vsyncTimestamp));
     return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
 }
 
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 95bc31f..5245556 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -16,11 +16,14 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <ftl/fake_guard.h>
 #include <scheduler/Fps.h>
 #include <scheduler/Timer.h>
 
 #include "VsyncSchedule.h"
 
+#include "ISchedulerCallback.h"
+#include "Utils/Dumper.h"
 #include "VSyncDispatchTimerQueue.h"
 #include "VSyncPredictor.h"
 #include "VSyncReactor.h"
@@ -54,18 +57,16 @@
 VsyncSchedule::VsyncSchedule(FeatureFlags features)
       : mTracker(createTracker()),
         mDispatch(createDispatch(*mTracker)),
-        mController(createController(*mTracker, features)) {
-    if (features.test(Feature::kTracePredictedVsync)) {
-        mTracer = std::make_unique<PredictedVsyncTracer>(*mDispatch);
-    }
-}
+        mController(createController(*mTracker, features)),
+        mTracer(features.test(Feature::kTracePredictedVsync)
+                        ? std::make_unique<PredictedVsyncTracer>(*mDispatch)
+                        : nullptr) {}
 
 VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller)
       : mTracker(std::move(tracker)),
         mDispatch(std::move(dispatch)),
         mController(std::move(controller)) {}
 
-VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default;
 VsyncSchedule::~VsyncSchedule() = default;
 
 Period VsyncSchedule::period() const {
@@ -77,6 +78,16 @@
 }
 
 void VsyncSchedule::dump(std::string& out) const {
+    utils::Dumper dumper(out);
+    {
+        std::lock_guard<std::mutex> lock(mHwVsyncLock);
+        dumper.dump("hwVsyncState", ftl::enum_string(mHwVsyncState));
+
+        ftl::FakeGuard guard(kMainThreadContext);
+        dumper.dump("pendingHwVsyncState", ftl::enum_string(mPendingHwVsyncState));
+        dumper.eol();
+    }
+
     out.append("VsyncController:\n");
     mController->dump(out);
 
@@ -120,4 +131,67 @@
     return reactor;
 }
 
+void VsyncSchedule::startPeriodTransition(ISchedulerCallback& callback, Period period) {
+    std::lock_guard<std::mutex> lock(mHwVsyncLock);
+    mController->startPeriodTransition(period.ns());
+    enableHardwareVsyncLocked(callback);
+}
+
+bool VsyncSchedule::addResyncSample(ISchedulerCallback& callback, TimePoint timestamp,
+                                    ftl::Optional<Period> hwcVsyncPeriod) {
+    bool needsHwVsync = false;
+    bool periodFlushed = false;
+    {
+        std::lock_guard<std::mutex> lock(mHwVsyncLock);
+        if (mHwVsyncState == HwVsyncState::Enabled) {
+            needsHwVsync = mController->addHwVsyncTimestamp(timestamp.ns(),
+                                                            hwcVsyncPeriod.transform(&Period::ns),
+                                                            &periodFlushed);
+        }
+    }
+    if (needsHwVsync) {
+        enableHardwareVsync(callback);
+    } else {
+        disableHardwareVsync(callback, false /* disallow */);
+    }
+    return periodFlushed;
+}
+
+void VsyncSchedule::enableHardwareVsync(ISchedulerCallback& callback) {
+    std::lock_guard<std::mutex> lock(mHwVsyncLock);
+    enableHardwareVsyncLocked(callback);
+}
+
+void VsyncSchedule::enableHardwareVsyncLocked(ISchedulerCallback& callback) {
+    if (mHwVsyncState == HwVsyncState::Disabled) {
+        getTracker().resetModel();
+        callback.setVsyncEnabled(true);
+        mHwVsyncState = HwVsyncState::Enabled;
+    }
+}
+
+void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) {
+    std::lock_guard<std::mutex> lock(mHwVsyncLock);
+    if (mHwVsyncState == HwVsyncState::Enabled) {
+        callback.setVsyncEnabled(false);
+    }
+    mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled;
+}
+
+bool VsyncSchedule::isHardwareVsyncAllowed(bool makeAllowed) {
+    std::lock_guard<std::mutex> lock(mHwVsyncLock);
+    if (makeAllowed && mHwVsyncState == HwVsyncState::Disallowed) {
+        mHwVsyncState = HwVsyncState::Disabled;
+    }
+    return mHwVsyncState != HwVsyncState::Disallowed;
+}
+
+void VsyncSchedule::setPendingHardwareVsyncState(bool enabled) {
+    mPendingHwVsyncState = enabled ? HwVsyncState::Enabled : HwVsyncState::Disabled;
+}
+
+bool VsyncSchedule::getPendingHardwareVsyncState() const {
+    return mPendingHwVsyncState == HwVsyncState::Enabled;
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 173b1d0..d88f1d1 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -19,6 +19,9 @@
 #include <memory>
 #include <string>
 
+#include <ThreadContext.h>
+#include <ftl/enum.h>
+#include <ftl/optional.h>
 #include <scheduler/Features.h>
 #include <scheduler/Time.h>
 
@@ -32,6 +35,8 @@
 
 namespace android::scheduler {
 
+struct ISchedulerCallback;
+
 // TODO(b/185535769): Rename classes, and remove aliases.
 class VSyncDispatch;
 class VSyncTracker;
@@ -44,12 +49,24 @@
 class VsyncSchedule {
 public:
     explicit VsyncSchedule(FeatureFlags);
-    VsyncSchedule(VsyncSchedule&&);
     ~VsyncSchedule();
 
     Period period() const;
     TimePoint vsyncDeadlineAfter(TimePoint) const;
 
+    // Inform the schedule that the period is changing and the schedule needs to recalibrate
+    // itself. The schedule will end the period transition internally. This will
+    // enable hardware VSYNCs in order to calibrate.
+    //
+    // \param [in] period   The period that the system is changing into.
+    void startPeriodTransition(ISchedulerCallback&, Period period);
+
+    // Pass a VSYNC sample to VsyncController. Return true if
+    // VsyncController detected that the VSYNC period changed. Enable or disable
+    // hardware VSYNCs depending on whether more samples are needed.
+    bool addResyncSample(ISchedulerCallback&, TimePoint timestamp,
+                         ftl::Optional<Period> hwcVsyncPeriod);
+
     // TODO(b/185535769): Hide behind API.
     const VsyncTracker& getTracker() const { return *mTracker; }
     VsyncTracker& getTracker() { return *mTracker; }
@@ -60,6 +77,22 @@
 
     void dump(std::string&) const;
 
+    // Turn on hardware VSYNCs, unless mHwVsyncState is Disallowed, in which
+    // case this call is ignored.
+    void enableHardwareVsync(ISchedulerCallback&) EXCLUDES(mHwVsyncLock);
+
+    // Disable hardware VSYNCs. If `disallow` is true, future calls to
+    // enableHardwareVsync are ineffective until allowHardwareVsync is called.
+    void disableHardwareVsync(ISchedulerCallback&, bool disallow) EXCLUDES(mHwVsyncLock);
+
+    // If true, enableHardwareVsync can enable hardware VSYNC (if not already
+    // enabled). If false, enableHardwareVsync does nothing.
+    bool isHardwareVsyncAllowed(bool makeAllowed) EXCLUDES(mHwVsyncLock);
+
+    void setPendingHardwareVsyncState(bool enabled) REQUIRES(kMainThreadContext);
+
+    bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext);
+
 private:
     friend class TestableScheduler;
     friend class android::EventThreadTest;
@@ -76,14 +109,36 @@
     static DispatchPtr createDispatch(VsyncTracker&);
     static ControllerPtr createController(VsyncTracker&, FeatureFlags);
 
+    void enableHardwareVsyncLocked(ISchedulerCallback&) REQUIRES(mHwVsyncLock);
+
+    mutable std::mutex mHwVsyncLock;
+    enum class HwVsyncState {
+        // Hardware VSYNCs are currently enabled.
+        Enabled,
+
+        // Hardware VSYNCs are currently disabled. They can be enabled by a call
+        // to `enableHardwareVsync`.
+        Disabled,
+
+        // Hardware VSYNCs are not currently allowed (e.g. because the display
+        // is off).
+        Disallowed,
+
+        ftl_last = Disallowed,
+    };
+    HwVsyncState mHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disallowed;
+
+    // Pending state, in case an attempt is made to set the state while the
+    // device is off.
+    HwVsyncState mPendingHwVsyncState GUARDED_BY(kMainThreadContext) = HwVsyncState::Disabled;
+
     class PredictedVsyncTracer;
     using TracerPtr = std::unique_ptr<PredictedVsyncTracer>;
 
-    // Effectively const except in move constructor.
-    TrackerPtr mTracker;
-    DispatchPtr mDispatch;
-    ControllerPtr mController;
-    TracerPtr mTracer;
+    const TrackerPtr mTracker;
+    const DispatchPtr mDispatch;
+    const ControllerPtr mController;
+    const TracerPtr mTracer;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
index bd4e3c2..ba1459a 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
@@ -26,7 +26,7 @@
 namespace scheduler {
 
 // TODO(b/185535769): Pull Clock.h to libscheduler to reuse this.
-using SchedulerClock = std::chrono::high_resolution_clock;
+using SchedulerClock = std::chrono::steady_clock;
 static_assert(SchedulerClock::is_steady);
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
new file mode 100644
index 0000000..3d0f1a9
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
@@ -0,0 +1,37 @@
+/*
+ * 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 <cstdint>
+
+#include <ftl/flags.h>
+
+namespace android {
+
+// Whether composition was covered by HWC and/or GPU.
+enum class CompositionCoverage : std::uint8_t {
+    Hwc = 1 << 0,
+
+    // Mutually exclusive: The composition either used the GPU, or reused a buffer that had been
+    // composited on the GPU.
+    Gpu = 1 << 1,
+    GpuReuse = 1 << 2,
+};
+
+using CompositionCoverageFlags = ftl::Flags<CompositionCoverage>;
+
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
new file mode 100644
index 0000000..cc41925
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -0,0 +1,43 @@
+/*
+ * 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 <scheduler/Time.h>
+#include <scheduler/VsyncId.h>
+
+namespace android {
+
+struct ICompositor {
+    // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL.
+    virtual void configure() = 0;
+
+    // Commits transactions for layers and displays. Returns whether any state has been invalidated,
+    // i.e. whether a frame should be composited for each display.
+    virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0;
+
+    // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
+    // via RenderEngine and the Composer HAL, respectively.
+    virtual void composite(TimePoint frameTime, VsyncId) = 0;
+
+    // Samples the composited frame via RegionSamplingThread.
+    virtual void sample() = 0;
+
+protected:
+    ~ICompositor() = default;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index 6d195b9..a1d5cd7 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -27,11 +27,8 @@
 std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs args) {
     std::shared_ptr<ScreenCaptureOutput> output = compositionengine::impl::createOutputTemplated<
             ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&,
-            std::unordered_set<compositionengine::LayerFE*>,
             const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine,
                                                                   args.renderArea,
-                                                                  std::move(
-                                                                          args.filterForScreenshot),
                                                                   args.colorProfile,
                                                                   args.regionSampling);
     output->editState().isSecure = args.renderArea.isSecure();
@@ -45,12 +42,11 @@
                     .setHasWideColorGamut(true)
                     .Build()));
 
-    ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags());
-    Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), args.renderArea.getReqHeight()};
-    output->setProjection(orientation, args.renderArea.getLayerStackSpaceRect(),
-                          orientedDisplaySpaceRect);
-
-    Rect sourceCrop = args.renderArea.getSourceCrop();
+    const Rect& sourceCrop = args.renderArea.getSourceCrop();
+    const ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags());
+    const Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(),
+                                        args.renderArea.getReqHeight()};
+    output->setProjection(orientation, sourceCrop, orientedDisplaySpaceRect);
     output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()});
 
     {
@@ -64,13 +60,9 @@
 }
 
 ScreenCaptureOutput::ScreenCaptureOutput(
-        const RenderArea& renderArea,
-        std::unordered_set<compositionengine::LayerFE*> filterForScreenshot,
-        const compositionengine::Output::ColorProfile& colorProfile, bool regionSampling)
-      : mRenderArea(renderArea),
-        mFilterForScreenshot(std::move(filterForScreenshot)),
-        mColorProfile(colorProfile),
-        mRegionSampling(regionSampling) {}
+        const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile,
+        bool regionSampling)
+      : mRenderArea(renderArea), mColorProfile(colorProfile), mRegionSampling(regionSampling) {}
 
 void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) {
     auto& outputState = editState();
@@ -115,9 +107,4 @@
     return clientCompositionLayers;
 }
 
-bool ScreenCaptureOutput::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
-    return mRenderArea.needsFiltering() ||
-            mFilterForScreenshot.find(&layer->getLayerFE()) != mFilterForScreenshot.end();
-}
-
 } // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
index 5dffc1d..4e5a0cc 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.h
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -33,7 +33,6 @@
     std::shared_ptr<renderengine::ExternalTexture> buffer;
     float sdrWhitePointNits;
     float displayBrightnessNits;
-    std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
     bool regionSampling;
 };
 
@@ -44,7 +43,6 @@
 class ScreenCaptureOutput : public compositionengine::impl::Output {
 public:
     ScreenCaptureOutput(const RenderArea& renderArea,
-                        std::unordered_set<compositionengine::LayerFE*> filterForScreenshot,
                         const compositionengine::Output::ColorProfile& colorProfile,
                         bool regionSampling);
 
@@ -54,15 +52,12 @@
             bool supportsProtectedContent, ui::Dataspace outputDataspace,
             std::vector<compositionengine::LayerFE*>& outLayerFEs) override;
 
-    bool layerNeedsFiltering(const compositionengine::OutputLayer*) const override;
-
 protected:
     bool getSkipColorTransform() const override { return false; }
     renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override;
 
 private:
     const RenderArea& mRenderArea;
-    const std::unordered_set<compositionengine::LayerFE*> mFilterForScreenshot;
     const compositionengine::Output::ColorProfile& mColorProfile;
     const bool mRegionSampling;
 };
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c7c980d..d818b8e 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -130,6 +130,7 @@
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerSnapshot.h"
 #include "HdrLayerInfoReporter.h"
 #include "Layer.h"
 #include "LayerProtoHelper.h"
@@ -172,6 +173,8 @@
 using namespace hardware::configstore;
 using namespace hardware::configstore::V1_0;
 using namespace sysprop;
+using ftl::Flags;
+using namespace ftl::flag_operators;
 
 using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
 using aidl::android::hardware::graphics::composer3::Capability;
@@ -407,15 +410,8 @@
 
     mDebugFlashDelay = base::GetUintProperty("debug.sf.showupdates"s, 0u);
 
-    // DDMS debugging deprecated (b/120782499)
-    property_get("debug.sf.ddms", value, "0");
-    int debugDdms = atoi(value);
-    ALOGI_IF(debugDdms, "DDMS debugging not supported");
-
-    property_get("debug.sf.enable_gl_backpressure", value, "1");
-    mPropagateBackpressureClientComposition = atoi(value);
-    ALOGI_IF(mPropagateBackpressureClientComposition,
-             "Enabling backpressure propagation for Client Composition");
+    mBackpressureGpuComposition = base::GetBoolProperty("debug.sf.enable_gl_backpressure"s, true);
+    ALOGI_IF(mBackpressureGpuComposition, "Enabling backpressure for GPU composition");
 
     property_get("ro.surface_flinger.supports_background_blur", value, "0");
     bool supportsBlurs = atoi(value);
@@ -476,6 +472,10 @@
     mPowerHintSessionMode =
             {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true),
              .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)};
+    mLayerLifecycleManagerEnabled =
+            base::GetBoolProperty("debug.sf.enable_layer_lifecycle_manager"s, false);
+    mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
+            base::GetBoolProperty("debug.sf.enable_legacy_frontend"s, true);
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -511,7 +511,8 @@
     mScheduler->run();
 }
 
-sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure) {
+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
     // display. Secure displays can show secure content so we add an additional restriction on it.
@@ -542,6 +543,7 @@
     DisplayDeviceState state;
     state.isSecure = secure;
     state.displayName = displayName;
+    state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
     mCurrentState.displays.add(token, state);
     return token;
 }
@@ -1975,13 +1977,14 @@
 // ----------------------------------------------------------------------------
 
 sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
-        gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) {
+        gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration,
+        const sp<IBinder>& layerHandle) {
     const auto& handle =
             vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
             ? mSfConnectionHandle
             : mAppConnectionHandle;
 
-    return mScheduler->createDisplayEventConnection(handle, eventRegistration);
+    return mScheduler->createDisplayEventConnection(handle, eventRegistration, layerHandle);
 }
 
 void SurfaceFlinger::scheduleCommit(FrameHint hint) {
@@ -2028,18 +2031,13 @@
 
     Mutex::Autolock lock(mStateLock);
 
-    if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) {
+    if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp);
+        displayIdOpt != mActiveDisplayId) {
+        // Ignore VSYNC for invalid/inactive displays.
         return;
     }
 
-    if (const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
-        displayId != mActiveDisplayId) {
-        // For now, we don't do anything with non active display vsyncs.
-        return;
-    }
-
-    bool periodFlushed = false;
-    mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed);
+    const bool periodFlushed = mScheduler->addResyncSample(timestamp, vsyncPeriod);
     if (periodFlushed) {
         mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted);
     }
@@ -2082,16 +2080,23 @@
     mScheduler->forceNextResync();
 }
 
+void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) {
+    // TODO(b/202734676) update refresh rate value on the RefreshRateOverlay
+}
+
 void SurfaceFlinger::setVsyncEnabled(bool enabled) {
     ATRACE_CALL();
 
     // On main thread to avoid race conditions with display power state.
     static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
-        mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
+        {
+            ftl::FakeGuard guard(kMainThreadContext);
+            mScheduler->getVsyncSchedule().setPendingHardwareVsyncState(enabled);
+        }
 
         if (const auto display = getDefaultDisplayDeviceLocked();
             display && display->isPoweredOn()) {
-            setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState);
+            setHWCVsyncEnabled(display->getPhysicalId(), enabled);
         }
     }));
 }
@@ -2134,6 +2139,110 @@
     }
 }
 
+bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update,
+                                                bool transactionsFlushed,
+                                                bool& outTransactionsAreEmpty) {
+    bool needsTraversal = false;
+    if (transactionsFlushed) {
+        needsTraversal |= commitMirrorDisplays(vsyncId);
+        needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates);
+        needsTraversal |= applyTransactions(update.transactions, vsyncId);
+    }
+    outTransactionsAreEmpty = !needsTraversal;
+    const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
+    if (shouldCommit) {
+        commitTransactions();
+    }
+
+    bool mustComposite = latchBuffers() || shouldCommit;
+    updateLayerGeometry();
+    return mustComposite;
+}
+
+bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update,
+                                          bool transactionsFlushed, bool& outTransactionsAreEmpty) {
+    using Changes = frontend::RequestedLayerState::Changes;
+    ATRACE_NAME("updateLayerSnapshots");
+    {
+        mLayerLifecycleManager.addLayers(std::move(update.newLayers));
+        mLayerLifecycleManager.applyTransactions(update.transactions);
+        mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles);
+        for (auto& legacyLayer : update.layerCreatedStates) {
+            sp<Layer> layer = legacyLayer.layer.promote();
+            if (layer) {
+                mLegacyLayers[layer->sequence] = layer;
+            }
+        }
+    }
+    if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) {
+        ATRACE_NAME("LayerHierarchyBuilder:update");
+        mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(),
+                                      mLayerLifecycleManager.getDestroyedLayers());
+    }
+
+    applyAndCommitDisplayTransactionStates(update.transactions);
+
+    {
+        ATRACE_NAME("LayerSnapshotBuilder:update");
+        frontend::LayerSnapshotBuilder::Args args{.root = mLayerHierarchyBuilder.getHierarchy(),
+                                                  .layerLifecycleManager = mLayerLifecycleManager,
+                                                  .displays = mFrontEndDisplayInfos,
+                                                  .displayChanges = mFrontEndDisplayInfosChanged,
+                                                  .globalShadowSettings =
+                                                          mDrawingState.globalShadowSettings,
+                                                  .supportsBlur = mSupportsBlur,
+                                                  .forceFullDamage = mForceFullDamage};
+        mLayerSnapshotBuilder.update(args);
+    }
+
+    if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input |
+                                                      Changes::Hierarchy)) {
+        mUpdateInputInfo = true;
+    }
+    if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy |
+                                                      Changes::Visibility)) {
+        mVisibleRegionsDirty = true;
+    }
+    outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0;
+    const bool mustComposite = mLayerLifecycleManager.getGlobalChanges().get() != 0;
+    {
+        ATRACE_NAME("LLM:commitChanges");
+        mLayerLifecycleManager.commitChanges();
+    }
+
+    if (!mLegacyFrontEndEnabled) {
+        ATRACE_NAME("DisplayCallbackAndStatsUpdates");
+        applyTransactions(update.transactions, vsyncId);
+
+        bool newDataLatched = false;
+        for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+            if (!snapshot->changes.test(Changes::Buffer)) continue;
+            auto it = mLegacyLayers.find(snapshot->sequence);
+            LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
+                                snapshot->getDebugString().c_str());
+            mLayersWithQueuedFrames.emplace(it->second);
+            newDataLatched = true;
+            if (!snapshot->isVisible) break;
+
+            Region visibleReg;
+            visibleReg.set(snapshot->transformedBoundsWithoutTransparentRegion);
+            invalidateLayerStack(snapshot->outputFilter, visibleReg);
+        }
+
+        for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
+            mLegacyLayers.erase(destroyedLayer->id);
+        }
+
+        // enter boot animation on first buffer latch
+        if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
+            ALOGI("Enter boot animation");
+            mBootStage = BootStage::BOOTANIMATION;
+        }
+        commitTransactions();
+    }
+    return mustComposite;
+}
+
 bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime)
         FTL_FAKE_GUARD(kMainThreadContext) {
     // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the
@@ -2154,11 +2263,11 @@
     const Period vsyncPeriod = mScheduler->getVsyncSchedule().period();
     const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod);
 
-    // When Backpressure propagation is enabled we want to give a small grace period
+    // When backpressure propagation is enabled, we want to give a small grace period of 1ms
     // for the present fence to fire instead of just giving up on this frame to handle cases
     // where present fence is just about to get signaled.
-    const int graceTimeForPresentFenceMs =
-            (mPropagateBackpressureClientComposition || !mHadClientComposition) ? 1 : 0;
+    const int graceTimeForPresentFenceMs = static_cast<int>(
+            mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu));
 
     // Pending frames may trigger backpressure propagation.
     const TracedOrdinal<bool> framePending = {"PrevFramePending",
@@ -2181,9 +2290,14 @@
                                                       (lastScheduledPresentTime.ns() <
                                                        previousPresentTime - frameMissedSlop))};
     const TracedOrdinal<bool> hwcFrameMissed = {"PrevHwcFrameMissed",
-                                                mHadDeviceComposition && frameMissed};
+                                                frameMissed &&
+                                                        mCompositionCoverage.test(
+                                                                CompositionCoverage::Hwc)};
+
     const TracedOrdinal<bool> gpuFrameMissed = {"PrevGpuFrameMissed",
-                                                mHadClientComposition && frameMissed};
+                                                frameMissed &&
+                                                        mCompositionCoverage.test(
+                                                                CompositionCoverage::Gpu)};
 
     if (frameMissed) {
         mFrameMissedCount++;
@@ -2221,7 +2335,7 @@
     }
 
     if (framePending) {
-        if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) {
+        if (mBackpressureGpuComposition || (hwcFrameMissed && !gpuFrameMissed)) {
             scheduleCommit(FrameHint::kNone);
             return false;
         }
@@ -2268,45 +2382,34 @@
         mFrameTimeline->setSfWakeUp(vsyncId.value, frameTime.ns(),
                                     Fps::fromPeriodNsecs(vsyncPeriod.ns()));
 
-        bool needsTraversal = false;
-        if (clearTransactionFlags(eTransactionFlushNeeded)) {
-            // Locking:
-            // 1. to prevent onHandleDestroyed from being called while the state lock is held,
-            // we must keep a copy of the transactions (specifically the composer
-            // states) around outside the scope of the lock.
-            // 2. Transactions and created layers do not share a lock. To prevent applying
-            // transactions with layers still in the createdLayer queue, flush the transactions
-            // before committing the created layers.
-            std::vector<TransactionState> transactions = mTransactionHandler.flushTransactions();
-            needsTraversal |= commitMirrorDisplays(vsyncId);
-            needsTraversal |= commitCreatedLayers(vsyncId);
-            needsTraversal |= applyTransactions(transactions, vsyncId);
+        const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
+        LifecycleUpdate updates;
+        if (flushTransactions) {
+            updates = flushLifecycleUpdates();
         }
-
-        const bool shouldCommit =
-                (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
-        if (shouldCommit) {
-            commitTransactions();
+        bool transactionsAreEmpty;
+        if (mLegacyFrontEndEnabled) {
+            mustComposite |= updateLayerSnapshotsLegacy(vsyncId, updates, flushTransactions,
+                                                        transactionsAreEmpty);
+        }
+        if (mLayerLifecycleManagerEnabled) {
+            mustComposite |=
+                    updateLayerSnapshots(vsyncId, updates, flushTransactions, transactionsAreEmpty);
         }
 
         if (transactionFlushNeeded()) {
             setTransactionFlags(eTransactionFlushNeeded);
         }
 
-        mustComposite |= shouldCommit;
-        mustComposite |= latchBuffers();
-
         // This has to be called after latchBuffers because we want to include the layers that have
         // been latched in the commit callback
-        if (!needsTraversal) {
+        if (transactionsAreEmpty) {
             // Invoke empty transaction callbacks early.
             mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
         } else {
             // Invoke OnCommit callbacks.
             mTransactionCallbackInvoker.sendCallbacks(true /* onCommitOnly */);
         }
-
-        updateLayerGeometry();
     }
 
     // Layers need to get updated (in the previous line) before we can use them for
@@ -2342,7 +2445,15 @@
     refreshArgs.outputs.reserve(displays.size());
     std::vector<DisplayId> displayIds;
     for (const auto& [_, display] : displays) {
-        refreshArgs.outputs.push_back(display->getCompositionDisplay());
+        bool dropFrame = false;
+        if (display->isVirtual()) {
+            Fps refreshRate = display->getAdjustedRefreshRate();
+            using fps_approx_ops::operator>;
+            dropFrame = (refreshRate > 0_Hz) && !mScheduler->isVsyncInPhase(frameTime, refreshRate);
+        }
+        if (!dropFrame) {
+            refreshArgs.outputs.push_back(display->getCompositionDisplay());
+        }
         displayIds.push_back(display->getId());
     }
     mPowerAdvisor->setDisplays(displayIds);
@@ -2385,15 +2496,6 @@
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
     refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
-    std::vector<Layer*> layers;
-
-    mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) {
-        if (auto layerFE = layer->getCompositionEngineLayerFE()) {
-            layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
-            refreshArgs.layers.push_back(layerFE);
-            layers.push_back(layer);
-        }
-    });
     refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags();
 
     if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) {
@@ -2420,17 +2522,13 @@
     // the scheduler.
     const auto presentTime = systemTime();
 
-    {
-        std::vector<LayerSnapshotGuard> layerSnapshotGuards;
-        for (Layer* layer : layers) {
-            layerSnapshotGuards.emplace_back(layer);
-        }
-        mCompositionEngine->present(refreshArgs);
-    }
+    std::vector<std::pair<Layer*, LayerFE*>> layers =
+            moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/false, vsyncId.value);
+    mCompositionEngine->present(refreshArgs);
+    moveSnapshotsFromCompositionArgs(refreshArgs, layers);
 
-    for (auto& layer : layers) {
-        CompositionResult compositionResult{
-                layer->getCompositionEngineLayerFE()->stealCompositionResult()};
+    for (auto [layer, layerFE] : layers) {
+        CompositionResult compositionResult{layerFE->stealCompositionResult()};
         layer->onPreComposition(compositionResult.refreshStartTime);
         for (auto releaseFence : compositionResult.releaseFences) {
             layer->onLayerDisplayed(releaseFence);
@@ -2458,29 +2556,43 @@
 
     postComposition(presentTime);
 
-    const bool prevFrameHadClientComposition = mHadClientComposition;
+    const bool hadGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu);
+    mCompositionCoverage.clear();
 
-    mHadClientComposition = mHadDeviceComposition = mReusedClientComposition = false;
     TimeStats::ClientCompositionRecord clientCompositionRecord;
     for (const auto& [_, display] : displays) {
         const auto& state = display->getCompositionDisplay()->getState();
-        mHadClientComposition |= state.usesClientComposition && !state.reusedClientComposition;
-        mHadDeviceComposition |= state.usesDeviceComposition;
-        mReusedClientComposition |= state.reusedClientComposition;
+
+        if (state.usesDeviceComposition) {
+            mCompositionCoverage |= CompositionCoverage::Hwc;
+        }
+
+        if (state.reusedClientComposition) {
+            mCompositionCoverage |= CompositionCoverage::GpuReuse;
+        } else if (state.usesClientComposition) {
+            mCompositionCoverage |= CompositionCoverage::Gpu;
+        }
+
         clientCompositionRecord.predicted |=
                 (state.strategyPrediction != CompositionStrategyPredictionState::DISABLED);
         clientCompositionRecord.predictionSucceeded |=
                 (state.strategyPrediction == CompositionStrategyPredictionState::SUCCESS);
     }
 
-    clientCompositionRecord.hadClientComposition = mHadClientComposition;
-    clientCompositionRecord.reused = mReusedClientComposition;
-    clientCompositionRecord.changed = prevFrameHadClientComposition != mHadClientComposition;
+    const bool hasGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu);
+
+    clientCompositionRecord.hadClientComposition = hasGpuComposited;
+    clientCompositionRecord.reused = mCompositionCoverage.test(CompositionCoverage::GpuReuse);
+    clientCompositionRecord.changed = hadGpuComposited != hasGpuComposited;
+
     mTimeStats->pushCompositionStrategyState(clientCompositionRecord);
 
-    // TODO: b/160583065 Enable skip validation when SF caches all client composition layers
-    const bool usedGpuComposition = mHadClientComposition || mReusedClientComposition;
-    mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition);
+    using namespace ftl::flag_operators;
+
+    // TODO(b/160583065): Enable skip validation when SF caches all client composition layers.
+    const bool hasGpuUseOrReuse =
+            mCompositionCoverage.any(CompositionCoverage::Gpu | CompositionCoverage::GpuReuse);
+    mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse);
 
     mLayersWithQueuedFrames.clear();
     if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
@@ -2488,7 +2600,7 @@
         mLayerTracing.notify(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value);
     }
 
-    mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp
+    if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true;
     mVisibleRegionsDirty = false;
 
     if (mCompositionEngine->needsAnotherUpdate()) {
@@ -2510,27 +2622,38 @@
     for (auto& layer : mLayersPendingRefresh) {
         Region visibleReg;
         visibleReg.set(layer->getScreenBounds());
-        invalidateLayerStack(layer, visibleReg);
+        invalidateLayerStack(layer->getOutputFilter(), visibleReg);
     }
     mLayersPendingRefresh.clear();
 }
 
-bool SurfaceFlinger::isHdrLayer(Layer* layer) const {
-    // Treat all layers as non-HDR if:
-    // 1. They do not have a valid HDR dataspace. Currently we treat those as PQ or HLG. and
-    // 2. The layer is allowed to be dimmed. WindowManager may disable dimming in order to
-    // keep animations invoking SDR screenshots of HDR layers seamless. Treat such tagged
-    // layers as HDR so that DisplayManagerService does not try to change the screen brightness
-    if (!isHdrDataspace(layer->getDataSpace()) && layer->isDimmingEnabled()) {
-        return false;
-    }
+bool SurfaceFlinger::isHdrLayer(const frontend::LayerSnapshot& snapshot) const {
+    // Even though the camera layer may be using an HDR transfer function or otherwise be "HDR"
+    // the device may need to avoid boosting the brightness as a result of these layers to
+    // reduce power consumption during camera recording
     if (mIgnoreHdrCameraLayers) {
-        auto buffer = layer->getBuffer();
-        if (buffer && (buffer->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) {
+        if (snapshot.externalTexture &&
+            (snapshot.externalTexture->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) {
             return false;
         }
     }
-    return true;
+    if (isHdrDataspace(snapshot.dataspace)) {
+        return true;
+    }
+    // If the layer is not allowed to be dimmed, treat it as HDR. WindowManager may disable
+    // dimming in order to keep animations invoking SDR screenshots of HDR layers seamless.
+    // Treat such tagged layers as HDR so that DisplayManagerService does not try to change
+    // the screen brightness
+    if (!snapshot.dimmingEnabled) {
+        return true;
+    }
+    // RANGE_EXTENDED layers may identify themselves as being "HDR" via a desired sdr/hdr ratio
+    if ((snapshot.dataspace & (int32_t)Dataspace::RANGE_MASK) ==
+                (int32_t)Dataspace::RANGE_EXTENDED &&
+        snapshot.desiredSdrHdrRatio > 1.01f) {
+        return true;
+    }
+    return false;
 }
 
 ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId,
@@ -2574,12 +2697,13 @@
     ATRACE_CALL();
     ALOGV(__func__);
 
-    const auto* display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
+    const auto* defaultDisplay = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
 
     std::shared_ptr<FenceTime> glCompositionDoneFenceTime;
-    if (display && display->getCompositionDisplay()->getState().usesClientComposition) {
+    if (defaultDisplay &&
+        defaultDisplay->getCompositionDisplay()->getState().usesClientComposition) {
         glCompositionDoneFenceTime =
-                std::make_shared<FenceTime>(display->getCompositionDisplay()
+                std::make_shared<FenceTime>(defaultDisplay->getCompositionDisplay()
                                                     ->getRenderSurface()
                                                     ->getClientTargetAcquireFence());
     } else {
@@ -2588,8 +2712,9 @@
 
     mPreviousPresentFences[1] = mPreviousPresentFences[0];
 
-    auto presentFence =
-            display ? getHwComposer().getPresentFence(display->getPhysicalId()) : Fence::NO_FENCE;
+    auto presentFence = defaultDisplay
+            ? getHwComposer().getPresentFence(defaultDisplay->getPhysicalId())
+            : Fence::NO_FENCE;
 
     auto presentFenceTime = std::make_shared<FenceTime>(presentFence);
     mPreviousPresentFences[0] = {presentFence, presentFenceTime};
@@ -2620,7 +2745,7 @@
                                             presentLatency.ns());
 
     for (const auto& layer: mLayersWithQueuedFrames) {
-        layer->onPostComposition(display, glCompositionDoneFenceTime, presentFenceTime,
+        layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime,
                                  compositorTiming);
         layer->releasePendingBuffer(presentTime.ns());
     }
@@ -2649,18 +2774,20 @@
         mAddingHDRLayerInfoListener = false;
     }
 
-    if (haveNewListeners || mSomeDataspaceChanged || mVisibleRegionsWereDirtyThisFrame) {
+    if (haveNewListeners || mHdrLayerInfoChanged) {
         for (auto& [compositionDisplay, listener] : hdrInfoListeners) {
             HdrLayerInfoReporter::HdrLayerInfo info;
             int32_t maxArea = 0;
             mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
                 const auto layerFe = layer->getCompositionEngineLayerFE();
-                if (layer->isVisible() &&
-                    compositionDisplay->includesLayer(layer->getOutputFilter())) {
-                    if (isHdrLayer(layer)) {
+                const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot();
+                if (snapshot.isVisible &&
+                    compositionDisplay->includesLayer(snapshot.outputFilter)) {
+                    if (isHdrLayer(snapshot)) {
                         const auto* outputLayer =
                             compositionDisplay->getOutputLayerForLayer(layerFe);
                         if (outputLayer) {
+                            info.mergeDesiredRatio(snapshot.desiredSdrHdrRatio);
                             info.numberOfHdrLayers++;
                             const auto displayFrame = outputLayer->getState().displayFrame;
                             const int32_t area = displayFrame.width() * displayFrame.height();
@@ -2677,8 +2804,7 @@
         }
     }
 
-    mSomeDataspaceChanged = false;
-    mVisibleRegionsWereDirtyThisFrame = false;
+    mHdrLayerInfoChanged = false;
 
     mTransactionCallbackInvoker.addPresentFence(std::move(presentFence));
     mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
@@ -2687,22 +2813,22 @@
     mTimeStats->incrementTotalFrames();
     mTimeStats->setPresentFenceGlobal(presentFenceTime);
 
-    const bool isInternalDisplay = display &&
+    const bool isInternalDisplay = defaultDisplay &&
             FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)
-                    .get(display->getPhysicalId())
+                    .get(defaultDisplay->getPhysicalId())
                     .transform(&PhysicalDisplay::isInternal)
                     .value_or(false);
 
-    if (isInternalDisplay && display && display->getPowerMode() == hal::PowerMode::ON &&
+    if (isInternalDisplay && defaultDisplay && defaultDisplay->getPowerMode() == hal::PowerMode::ON &&
         presentFenceTime->isValid()) {
         mScheduler->addPresentFence(std::move(presentFenceTime));
     }
 
     const bool isDisplayConnected =
-            display && getHwComposer().isConnected(display->getPhysicalId());
+            defaultDisplay && getHwComposer().isConnected(defaultDisplay->getPhysicalId());
 
     if (!hasSyncFramework) {
-        if (isDisplayConnected && display->isPoweredOn()) {
+        if (isDisplayConnected && defaultDisplay->isPoweredOn()) {
             mScheduler->enableHardwareVsync();
         }
     }
@@ -2711,7 +2837,7 @@
     const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle);
     mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections);
 
-    if (isDisplayConnected && !display->isPoweredOn()) {
+    if (isDisplayConnected && !defaultDisplay->isPoweredOn()) {
         getRenderEngine().cleanupPostRender();
         return;
     }
@@ -2737,13 +2863,24 @@
     }
 
     if (mNumTrustedPresentationListeners > 0) {
+        display::DisplayMap<ui::LayerStack, const DisplayDevice*> layerStackToDisplay;
+        {
+            Mutex::Autolock lock(mStateLock);
+            for (const auto& [token, display] : mDisplays) {
+                layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get());
+            }
+        }
+
         // We avoid any reverse traversal upwards so this shouldn't be too expensive
         mDrawingState.traverse([&](Layer* layer) {
             if (!layer->hasTrustedPresentationListener()) {
                 return;
             }
-            layer->updateTrustedPresentationState(display, nanoseconds_to_milliseconds(callTime),
-                                                  false);
+            const std::optional<const DisplayDevice*> displayOpt =
+                    layerStackToDisplay.get(layer->getLayerSnapshot()->outputFilter.layerStack);
+            const DisplayDevice* display = displayOpt.value_or(nullptr);
+            layer->updateTrustedPresentationState(display, layer->getLayerSnapshot(),
+                                                  nanoseconds_to_milliseconds(callTime), false);
         });
     }
 
@@ -2996,15 +3133,15 @@
 
         const auto enableFrameRateOverride = [&] {
             using Config = scheduler::RefreshRateSelector::Config;
-            if (!sysprop::enable_frame_rate_override(false)) {
+            if (!sysprop::enable_frame_rate_override(true)) {
                 return Config::FrameRateOverride::Disabled;
             }
 
-            if (sysprop::frame_rate_override_for_native_rates(true)) {
+            if (sysprop::frame_rate_override_for_native_rates(false)) {
                 return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
             }
 
-            if (!sysprop::frame_rate_override_global(false)) {
+            if (!sysprop::frame_rate_override_global(true)) {
                 return Config::FrameRateOverride::AppOverride;
             }
 
@@ -3069,6 +3206,8 @@
     creationArgs.initialPowerMode =
             state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt;
 
+    creationArgs.requestedRefreshRate = state.requestedRefreshRate;
+
     sp<DisplayDevice> display = getFactory().createDisplayDevice(creationArgs);
 
     nativeWindowSurface->preallocateBuffers();
@@ -3185,6 +3324,10 @@
         dispatchDisplayHotplugEvent(displayId, true);
     }
 
+    if (display->isVirtual()) {
+        display->adjustRefreshRate(mScheduler->getLeaderRefreshRate());
+    }
+
     mDisplays.try_emplace(displayToken, std::move(display));
 }
 
@@ -3336,7 +3479,8 @@
 void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) {
     // Commit display transactions.
     const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded;
-    if (displayTransactionNeeded) {
+    mFrontEndDisplayInfosChanged = displayTransactionNeeded;
+    if (displayTransactionNeeded && !mLayerLifecycleManagerEnabled) {
         processDisplayChangesLocked();
         mFrontEndDisplayInfos.clear();
         for (const auto& [_, display] : mDisplays) {
@@ -3427,7 +3571,7 @@
                 // this layer is not visible anymore
                 Region visibleReg;
                 visibleReg.set(layer->getScreenBounds());
-                invalidateLayerStack(sp<Layer>::fromExisting(layer), visibleReg);
+                invalidateLayerStack(layer->getOutputFilter(), visibleReg);
             }
         });
     }
@@ -3515,16 +3659,23 @@
     outWindowInfos.reserve(sNumWindowInfos);
     sNumWindowInfos = 0;
 
-    mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
-        if (!layer->needsInputInfo()) return;
+    if (mLayerLifecycleManagerEnabled) {
+        mLayerSnapshotBuilder.forEachInputSnapshot(
+                [&outWindowInfos](const frontend::LayerSnapshot& snapshot) {
+                    outWindowInfos.push_back(snapshot.inputInfo);
+                });
+    } else {
+        mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
+            if (!layer->needsInputInfo()) return;
+            const auto opt =
+                    mFrontEndDisplayInfos.get(layer->getLayerStack())
+                            .transform([](const frontend::DisplayInfo& info) {
+                                return Layer::InputDisplayArgs{&info.transform, info.isSecure};
+                            });
 
-        const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack())
-                                 .transform([](const frontend::DisplayInfo& info) {
-                                     return Layer::InputDisplayArgs{&info.transform, info.isSecure};
-                                 });
-
-        outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{})));
-    });
+            outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{})));
+        });
+    }
 
     sNumWindowInfos = outWindowInfos.size();
 
@@ -3541,17 +3692,9 @@
             refreshArgs.outputs.push_back(display->getCompositionDisplay());
         }
     }
-
-    std::vector<LayerSnapshotGuard> layerSnapshotGuards;
-    mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) {
-        if (layer->getLayerSnapshot()->compositionType ==
-            aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
-            layer->updateSnapshot(false /* updateGeometry */);
-            layerSnapshotGuards.emplace_back(layer);
-        }
-    });
-
+    auto layers = moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/true, 0);
     mCompositionEngine->updateCursorAsync(refreshArgs);
+    moveSnapshotsFromCompositionArgs(refreshArgs, layers);
 }
 
 void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest> modeRequests) {
@@ -3727,10 +3870,10 @@
     }
 }
 
-void SurfaceFlinger::invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty) {
+void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) {
     for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         auto display = displayDevice->getCompositionDisplay();
-        if (display->includesLayer(layer->getOutputFilter())) {
+        if (display->includesLayer(layerFilter)) {
             display->editState().dirtyRegion.orSelf(dirty);
         }
     }
@@ -3850,6 +3993,7 @@
     {
         std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
         mCreatedLayers.emplace_back(layer, parent, args.addToRoot);
+        mNewLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
     }
 
     setTransactionFlags(eTransactionNeeded);
@@ -3861,14 +4005,18 @@
 }
 
 uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) {
-    return mTransactionFlags.fetch_and(~mask) & mask;
+    uint32_t transactionFlags = mTransactionFlags.fetch_and(~mask);
+    ATRACE_INT("mTransactionFlags", transactionFlags);
+    return transactionFlags & mask;
 }
 
 void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule,
                                          const sp<IBinder>& applyToken, FrameHint frameHint) {
     mScheduler->modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken);
+    uint32_t transactionFlags = mTransactionFlags.fetch_or(mask);
+    ATRACE_INT("mTransactionFlags", transactionFlags);
 
-    if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) {
+    if (const bool scheduled = transactionFlags & mask; !scheduled) {
         scheduleCommit(frameHint);
     } else if (frameHint == FrameHint::kActive) {
         // Even if the next frame is already scheduled, we should reset the idle timer
@@ -3916,16 +4064,30 @@
         sp<Layer> layer = LayerHandle::getLayer(s.surface);
         const auto& transaction = *flushState.transaction;
         // check for barrier frames
-        if (s.bufferData->hasBarrier &&
-            ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) {
-            const bool willApplyBarrierFrame =
-                    flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
-                    (flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
-                     s.bufferData->barrierFrameNumber);
-            if (!willApplyBarrierFrame) {
-                ATRACE_NAME("NotReadyBarrier");
-                ready = TransactionReadiness::NotReadyBarrier;
-                return false;
+        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->getDrawingState().producerId > s.bufferData->producerId) {
+                layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener,
+                                                 s.bufferData->buffer, s.bufferData->frameNumber,
+                                                 s.bufferData->acquireFence);
+                // 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_NAME("DeleteStaleBuffer");
+                return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
+            }
+
+            if (layer->getDrawingState().frameNumber < s.bufferData->barrierFrameNumber) {
+                const bool willApplyBarrierFrame =
+                        flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
+                        ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
+                          s.bufferData->barrierFrameNumber));
+                if (!willApplyBarrierFrame) {
+                    ATRACE_NAME("NotReadyBarrier");
+                    ready = TransactionReadiness::NotReadyBarrier;
+                    return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                }
             }
         }
 
@@ -3936,7 +4098,7 @@
         if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
             ATRACE_NAME("hasPendingBuffer");
             ready = TransactionReadiness::NotReady;
-            return false;
+            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
         }
 
         // check fence status
@@ -3963,14 +4125,14 @@
                                                        "Buffer processing hung up due to stuck "
                                                        "fence. Indicates GPU hang");
                 }
-                return false;
+                return TraverseBuffersReturnValues::STOP_TRAVERSAL;
             }
 
             ready = enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer
                     ? TransactionReadiness::ReadyUnsignaledSingle
                     : TransactionReadiness::ReadyUnsignaled;
         }
-        return true;
+        return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
     });
     ATRACE_INT("TransactionReadiness", static_cast<int>(ready));
     return ready;
@@ -4005,7 +4167,7 @@
                                       transaction.displays, transaction.flags,
                                       transaction.inputWindowCommands,
                                       transaction.desiredPresentTime, transaction.isAutoTimestamp,
-                                      std::move(transaction.uncacheBufferIds), transaction.postTime,
+                                      transaction.buffer, transaction.postTime,
                                       transaction.permissions, transaction.hasListenerCallbacks,
                                       transaction.listenerCallbacks, transaction.originPid,
                                       transaction.originUid, transaction.id);
@@ -4093,9 +4255,8 @@
         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, const std::vector<ListenerCallbacks>& listenerCallbacks,
-        uint64_t transactionId) {
+        bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
     ATRACE_CALL();
 
     uint32_t permissions =
@@ -4129,15 +4290,6 @@
     const int originPid = ipc->getCallingPid();
     const int originUid = ipc->getCallingUid();
 
-    std::vector<uint64_t> uncacheBufferIds;
-    uncacheBufferIds.reserve(uncacheBuffers.size());
-    for (const auto& uncacheBuffer : uncacheBuffers) {
-        sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
-        if (buffer != nullptr) {
-            uncacheBufferIds.push_back(buffer->getId());
-        }
-    }
-
     std::vector<ResolvedComposerState> resolvedStates;
     resolvedStates.reserve(states.size());
     for (auto& state : states) {
@@ -4155,22 +4307,14 @@
         }
     }
 
-    TransactionState state{frameTimelineInfo,
-                           resolvedStates,
-                           displays,
-                           flags,
-                           applyToken,
-                           inputWindowCommands,
-                           desiredPresentTime,
-                           isAutoTimestamp,
-                           std::move(uncacheBufferIds),
-                           postTime,
-                           permissions,
-                           hasListenerCallbacks,
-                           listenerCallbacks,
-                           originPid,
-                           originUid,
-                           transactionId};
+    TransactionState state{frameTimelineInfo,  resolvedStates,
+                           displays,           flags,
+                           applyToken,         inputWindowCommands,
+                           desiredPresentTime, isAutoTimestamp,
+                           uncacheBuffer,      postTime,
+                           permissions,        hasListenerCallbacks,
+                           listenerCallbacks,  originPid,
+                           originUid,          transactionId};
 
     if (mTransactionTracing) {
         mTransactionTracing->addQueuedTransaction(state);
@@ -4183,9 +4327,8 @@
     }(state.flags);
 
     const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone;
-    setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint);
     mTransactionHandler.queueTransaction(std::move(state));
-
+    setTransactionFlags(eTransactionFlushNeeded, schedule, applyToken, frameHint);
     return NO_ERROR;
 }
 
@@ -4194,15 +4337,17 @@
                                            Vector<DisplayState>& displays, uint32_t flags,
                                            const InputWindowCommands& inputWindowCommands,
                                            const int64_t desiredPresentTime, bool isAutoTimestamp,
-                                           const std::vector<uint64_t>& uncacheBufferIds,
+                                           const client_cache_t& uncacheBuffer,
                                            const int64_t postTime, uint32_t permissions,
                                            bool hasListenerCallbacks,
                                            const std::vector<ListenerCallbacks>& listenerCallbacks,
                                            int originPid, int originUid, uint64_t transactionId) {
     uint32_t transactionFlags = 0;
-    for (DisplayState& display : displays) {
-        display.sanitize(permissions);
-        transactionFlags |= setDisplayStateLocked(display);
+    if (!mLayerLifecycleManagerEnabled) {
+        for (DisplayState& display : displays) {
+            display.sanitize(permissions);
+            transactionFlags |= setDisplayStateLocked(display);
+        }
     }
 
     // start and end registration for listeners w/ no surface so they can get their callback.  Note
@@ -4214,9 +4359,16 @@
 
     uint32_t clientStateFlags = 0;
     for (auto& resolvedState : states) {
-        clientStateFlags |=
-                setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
-                                     isAutoTimestamp, postTime, permissions, transactionId);
+        if (mLegacyFrontEndEnabled) {
+            clientStateFlags |=
+                    setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
+                                         isAutoTimestamp, postTime, permissions, transactionId);
+
+        } else /*mLayerLifecycleManagerEnabled*/ {
+            clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState,
+                                                             desiredPresentTime, isAutoTimestamp,
+                                                             postTime, permissions, transactionId);
+        }
         if ((flags & eAnimation) && resolvedState.state.surface) {
             if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
                 using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
@@ -4235,8 +4387,11 @@
         ALOGE("Only privileged callers are allowed to send input commands.");
     }
 
-    for (uint64_t uncacheBufferId : uncacheBufferIds) {
-        mBufferIdsToUncache.push_back(uncacheBufferId);
+    if (uncacheBuffer.isValid()) {
+        sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
+        if (buffer != nullptr) {
+            mBufferIdsToUncache.push_back(buffer->getId());
+        }
     }
 
     // If a synchronous transaction is explicitly requested without any changes, force a transaction
@@ -4249,8 +4404,8 @@
 
     bool needsTraversal = false;
     if (transactionFlags) {
-        // We are on the main thread, we are about to preform a traversal. Clear the traversal bit
-        // so we don't have to wake up again next frame to preform an unnecessary traversal.
+        // We are on the main thread, we are about to perform a traversal. Clear the traversal bit
+        // so we don't have to wake up again next frame to perform an unnecessary traversal.
         if (transactionFlags & eTraversalNeeded) {
             transactionFlags = transactionFlags & (~eTraversalNeeded);
             needsTraversal = true;
@@ -4263,6 +4418,42 @@
     return needsTraversal;
 }
 
+bool SurfaceFlinger::applyAndCommitDisplayTransactionStates(
+        std::vector<TransactionState>& transactions) {
+    Mutex::Autolock _l(mStateLock);
+    bool needsTraversal = false;
+    uint32_t transactionFlags = 0;
+    for (auto& transaction : transactions) {
+        for (DisplayState& display : transaction.displays) {
+            display.sanitize(transaction.permissions);
+            transactionFlags |= setDisplayStateLocked(display);
+        }
+    }
+
+    if (transactionFlags) {
+        // We are on the main thread, we are about to perform a traversal. Clear the traversal bit
+        // so we don't have to wake up again next frame to perform an unnecessary traversal.
+        if (transactionFlags & eTraversalNeeded) {
+            transactionFlags = transactionFlags & (~eTraversalNeeded);
+            needsTraversal = true;
+        }
+        if (transactionFlags) {
+            setTransactionFlags(transactionFlags);
+        }
+    }
+
+    mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded;
+    if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) {
+        processDisplayChangesLocked();
+        mFrontEndDisplayInfos.clear();
+        for (const auto& [_, display] : mDisplays) {
+            mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo());
+        }
+    }
+
+    return needsTraversal;
+}
+
 uint32_t SurfaceFlinger::setDisplayStateLocked(const DisplayState& s) {
     const ssize_t index = mCurrentState.displays.indexOfKey(s.token);
     if (index < 0) return 0;
@@ -4366,8 +4557,10 @@
     }
     if (layer == nullptr) {
         for (auto& [listener, callbackIds] : s.listeners) {
-            mTransactionCallbackInvoker.registerUnpresentedCallbackHandle(
-                    sp<CallbackHandle>::make(listener, callbackIds, s.surface));
+            mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
+                                                                                   callbackIds,
+                                                                                   s.surface),
+                                                          std::vector<JankData>());
         }
         return 0;
     }
@@ -4497,9 +4690,6 @@
     if (what & layer_state_t::eDataspaceChanged) {
         if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded;
     }
-    if (what & layer_state_t::eHdrMetadataChanged) {
-        if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded;
-    }
     if (what & layer_state_t::eSurfaceDamageRegionChanged) {
         if (layer->setSurfaceDamageRegion(s.surfaceDamageRegion)) flags |= eTraversalNeeded;
     }
@@ -4573,6 +4763,14 @@
     if (what & layer_state_t::eDimmingEnabledChanged) {
         if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded;
     }
+    if (what & layer_state_t::eExtendedRangeBrightnessChanged) {
+        if (layer->setExtendedRangeBrightness(s.currentSdrHdrRatio, s.desiredSdrHdrRatio)) {
+            flags |= eTraversalNeeded;
+        }
+    }
+    if (what & layer_state_t::eHdrMetadataChanged) {
+        if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded;
+    }
     if (what & layer_state_t::eTrustedOverlayChanged) {
         if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
             flags |= eTraversalNeeded;
@@ -4639,7 +4837,15 @@
                                           s.trustedPresentationListener);
     }
 
-    if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded;
+    if (what & layer_state_t::eFlushJankData) {
+        // Do nothing. Processing the transaction completed listeners currently cause the flush.
+    }
+
+    if (layer->setTransactionCompletedListeners(callbackHandles,
+                                                layer->willPresentCurrentTransaction())) {
+        flags |= eTraversalNeeded;
+    }
+
     // Do not put anything that updates layer state or modifies flags after
     // setTransactionCompletedListener
 
@@ -4652,6 +4858,96 @@
     return flags;
 }
 
+uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& frameTimelineInfo,
+                                                      ResolvedComposerState& composerState,
+                                                      int64_t desiredPresentTime,
+                                                      bool isAutoTimestamp, int64_t postTime,
+                                                      uint32_t permissions,
+                                                      uint64_t transactionId) {
+    layer_state_t& s = composerState.state;
+    s.sanitize(permissions);
+    const nsecs_t latchTime = systemTime();
+    bool unused;
+
+    std::vector<ListenerCallbacks> filteredListeners;
+    for (auto& listener : s.listeners) {
+        // Starts a registration but separates the callback ids according to callback type. This
+        // allows the callback invoker to send on latch callbacks earlier.
+        // note that startRegistration will not re-register if the listener has
+        // already be registered for a prior surface control
+
+        ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT);
+        if (!onCommitCallbacks.callbackIds.empty()) {
+            filteredListeners.push_back(onCommitCallbacks);
+        }
+
+        ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE);
+        if (!onCompleteCallbacks.callbackIds.empty()) {
+            filteredListeners.push_back(onCompleteCallbacks);
+        }
+    }
+
+    const uint64_t what = s.what;
+    uint32_t flags = 0;
+    sp<Layer> layer = nullptr;
+    if (s.surface) {
+        layer = LayerHandle::getLayer(s.surface);
+    } else {
+        // The client may provide us a null handle. Treat it as if the layer was removed.
+        ALOGW("Attempt to set client state with a null layer handle");
+    }
+    if (layer == nullptr) {
+        for (auto& [listener, callbackIds] : s.listeners) {
+            mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
+                                                                                   callbackIds,
+                                                                                   s.surface),
+                                                          std::vector<JankData>());
+        }
+        return 0;
+    }
+    if (what & layer_state_t::eProducerDisconnect) {
+        layer->onDisconnect();
+    }
+    std::optional<nsecs_t> dequeueBufferTimestamp;
+    if (what & layer_state_t::eMetadataChanged) {
+        dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME);
+    }
+
+    std::vector<sp<CallbackHandle>> callbackHandles;
+    if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) {
+        for (auto& [listener, callbackIds] : filteredListeners) {
+            callbackHandles.emplace_back(
+                    sp<CallbackHandle>::make(listener, callbackIds, s.surface));
+        }
+    }
+    if (what & layer_state_t::eSidebandStreamChanged) {
+        if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
+    }
+    if (what & layer_state_t::eBufferChanged) {
+        if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
+                             desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
+                             frameTimelineInfo)) {
+            layer->latchBuffer(unused, latchTime);
+            flags |= eTraversalNeeded;
+        }
+        mLayersWithQueuedFrames.emplace(layer);
+    } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
+        layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime);
+    }
+
+    if (what & layer_state_t::eTrustedPresentationInfoChanged) {
+        layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
+                                          s.trustedPresentationListener);
+    }
+
+    const auto& snapshot = mLayerSnapshotBuilder.getSnapshot(layer->getSequence());
+    bool willPresentCurrentTransaction =
+            snapshot && (snapshot->hasReadyFrame || snapshot->sidebandStreamHasFrame);
+    if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction))
+        flags |= eTraversalNeeded;
+    return flags;
+}
+
 uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) {
     bool hasChanges = mInputWindowCommands.merge(inputWindowCommands);
     return hasChanges ? eTraversalNeeded : 0;
@@ -4720,6 +5016,7 @@
         LayerCreationArgs mirrorArgs(args);
         mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill;
         mirrorArgs.addToRoot = true;
+        mirrorArgs.layerStackToMirror = layerStack;
         result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer);
         outResult.layerId = rootMirrorLayer->sequence;
         outResult.layerName = String16(rootMirrorLayer->getDebugName());
@@ -4822,7 +5119,12 @@
     setTransactionFlags(eTransactionNeeded);
 }
 
-void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t /* layerId */) {
+void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
+    {
+        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+        mDestroyedHandles.emplace_back(layerId);
+    }
+
     Mutex::Autolock lock(mStateLock);
     markLayerPendingRemovalLocked(layer);
     mBufferCountTracker.remove(handle);
@@ -4830,6 +5132,8 @@
     if (mTransactionTracing) {
         mTransactionTracing->onHandleRemoved(handle);
     }
+
+    setTransactionFlags(eTransactionFlushNeeded);
 }
 
 void SurfaceFlinger::onInitializeDisplays() {
@@ -4930,7 +5234,8 @@
         }
         getHwComposer().setPowerMode(displayId, mode);
         if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) {
-            setHWCVsyncEnabled(displayId, mHWCVsyncPendingState);
+            setHWCVsyncEnabled(displayId,
+                               mScheduler->getVsyncSchedule().getPendingHardwareVsyncState());
             mScheduler->onScreenAcquired(mAppConnectionHandle);
             mScheduler->resyncToHardwareVsync(true, refreshRate);
         }
@@ -4951,7 +5256,7 @@
         }
 
         // Make sure HWVsync is disabled before turning off the display
-        setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE);
+        setHWCVsyncEnabled(displayId, false);
 
         getHwComposer().setPowerMode(displayId, mode);
         mVisibleRegionsDirty = true;
@@ -5160,14 +5465,6 @@
 
     mScheduler->dump(dumper);
 
-    // TODO(b/241286146): Move to Scheduler.
-    {
-        utils::Dumper::Indent indent(dumper);
-        dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState);
-        dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState);
-    }
-    dumper.eol();
-
     // TODO(b/241285876): Move to DisplayModeController.
     dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor);
     dumper.eol();
@@ -6399,11 +6696,17 @@
                                          args.useIdentityTransform, args.captureSecureLayers);
     });
 
-    auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) {
-        traverseLayersInLayerStack(layerStack, args.uid, visitor);
-    };
+    GetLayerSnapshotsFunction getLayerSnapshots;
+    if (mLayerLifecycleManagerEnabled) {
+        getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, args.uid);
+    } else {
+        auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) {
+            traverseLayersInLayerStack(layerStack, args.uid, visitor);
+        };
+        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    }
 
-    auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
+    auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize,
                                       args.pixelFormat, args.allowProtected, args.grayscale,
                                       captureListener);
     return fenceStatus(future.get());
@@ -6435,9 +6738,15 @@
                                          false /* captureSecureLayers */);
     });
 
-    auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
-        traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor);
-    };
+    GetLayerSnapshotsFunction getLayerSnapshots;
+    if (mLayerLifecycleManagerEnabled) {
+        getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID);
+    } else {
+        auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
+            traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor);
+        };
+        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    }
 
     if (captureListener == nullptr) {
         ALOGE("capture screen must provide a capture listener callback");
@@ -6447,7 +6756,7 @@
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
-    auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, size,
+    auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size,
                                       ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale,
                                       captureListener);
     return fenceStatus(future.get());
@@ -6465,7 +6774,7 @@
     ui::Size reqSize;
     sp<Layer> parent;
     Rect crop(args.sourceCrop);
-    std::unordered_set<sp<Layer>, SpHash<Layer>> excludeLayers;
+    std::unordered_set<uint32_t> excludeLayerIds;
     ui::Dataspace dataspace;
 
     // Call this before holding mStateLock to avoid any deadlocking.
@@ -6505,9 +6814,9 @@
         reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
 
         for (const auto& handle : args.excludeHandles) {
-            sp<Layer> excludeLayer = LayerHandle::getLayer(handle);
-            if (excludeLayer != nullptr) {
-                excludeLayers.emplace(excludeLayer);
+            uint32_t excludeLayer = LayerHandle::getLayerId(handle);
+            if (excludeLayer != UNASSIGNED_LAYER_ID) {
+                excludeLayerIds.emplace(excludeLayer);
             } else {
                 ALOGW("Invalid layer handle passed as excludeLayer to captureLayers");
                 return NAME_NOT_FOUND;
@@ -6527,50 +6836,56 @@
         return BAD_VALUE;
     }
 
-    Rect layerStackSpaceRect(crop.left, crop.top, crop.left + reqSize.width,
-                             crop.top + reqSize.height);
     bool childrenOnly = args.childrenOnly;
     RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
         return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
-                                                 childrenOnly, layerStackSpaceRect,
-                                                 args.captureSecureLayers);
+                                                 childrenOnly, args.captureSecureLayers);
     });
-
-    auto traverseLayers = [parent, args, excludeLayers](const LayerVector::Visitor& visitor) {
-        parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
-            if (!layer->isVisible()) {
-                return;
-            } else if (args.childrenOnly && layer == parent.get()) {
-                return;
-            } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) {
-                return;
-            }
-
-            auto p = sp<Layer>::fromExisting(layer);
-            while (p != nullptr) {
-                if (excludeLayers.count(p) != 0) {
+    GetLayerSnapshotsFunction getLayerSnapshots;
+    if (mLayerLifecycleManagerEnabled) {
+        FloatRect parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height)
+                                              : crop.toFloatRect();
+        getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid,
+                                                            std::move(excludeLayerIds),
+                                                            args.childrenOnly, parentCrop);
+    } else {
+        auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) {
+            parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
+                if (!layer->isVisible()) {
+                    return;
+                } else if (args.childrenOnly && layer == parent.get()) {
+                    return;
+                } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) {
                     return;
                 }
-                p = p->getParent();
-            }
 
-            visitor(layer);
-        });
-    };
+                auto p = sp<Layer>::fromExisting(layer);
+                while (p != nullptr) {
+                    if (excludeLayerIds.count(p->sequence) != 0) {
+                        return;
+                    }
+                    p = p->getParent();
+                }
+
+                visitor(layer);
+            });
+        };
+        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    }
 
     if (captureListener == nullptr) {
         ALOGE("capture screen must provide a capture listener callback");
         return BAD_VALUE;
     }
 
-    auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
+    auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize,
                                       args.pixelFormat, args.allowProtected, args.grayscale,
                                       captureListener);
     return fenceStatus(future.get());
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
-        RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
+        RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
         ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale,
         const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
@@ -6589,15 +6904,18 @@
     const bool supportsProtected = getRenderEngine().supportsProtectedContent();
     bool hasProtectedLayer = false;
     if (allowProtected && supportsProtected) {
-        auto future = mScheduler->schedule([=]() {
-            bool protectedLayerFound = false;
-            traverseLayers([&](Layer* layer) {
-                protectedLayerFound =
-                        protectedLayerFound || (layer->isVisible() && layer->isProtected());
-            });
-            return protectedLayerFound;
-        });
-        hasProtectedLayer = future.get();
+        hasProtectedLayer = mScheduler
+                                    ->schedule([=]() {
+                                        bool protectedLayerFound = false;
+                                        auto layers = getLayerSnapshots();
+                                        for (auto& [layer, layerFe] : layers) {
+                                            protectedLayerFound |=
+                                                    (layerFe->mSnapshot->isVisible &&
+                                                     layerFe->mSnapshot->hasProtectedContent);
+                                        }
+                                        return protectedLayerFound;
+                                    })
+                                    .get();
     }
 
     const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
@@ -6622,12 +6940,12 @@
             renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
                                                  renderengine::impl::ExternalTexture::Usage::
                                                          WRITEABLE);
-    return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, texture,
+    return captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
                                false /* regionSampling */, grayscale, captureListener);
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
-        RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
+        RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
@@ -6650,7 +6968,7 @@
 
                 ftl::SharedFuture<FenceResult> renderFuture;
                 renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
-                    renderFuture = renderScreenImpl(renderArea, traverseLayers, buffer,
+                    renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer,
                                                     canCaptureBlackoutContent, regionSampling,
                                                     grayscale, captureResults);
                 });
@@ -6678,18 +6996,20 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        std::shared_ptr<const RenderArea> renderArea, TraverseLayersFunction traverseLayers,
+        std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
         ScreenCaptureResults& captureResults) {
     ATRACE_CALL();
 
-    size_t layerCount = 0;
-    traverseLayers([&](Layer* layer) {
-        layerCount++;
-        captureResults.capturedSecureLayers =
-                captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure());
-    });
+    auto layers = getLayerSnapshots();
+    for (auto& [layer, layerFE] : layers) {
+        frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
+        captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure);
+        captureResults.capturedHdrLayers |= isHdrLayer(*snapshot);
+        layerFE->mSnapshot->geomLayerTransform =
+                renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
+    }
 
     // We allow the system server to take screenshots of secure layers for
     // use in situations like the Screen-rotation animation and place
@@ -6723,31 +7043,6 @@
     }
     captureResults.capturedDataspace = dataspace;
 
-    const auto transform = renderArea->getTransform();
-    const auto display = renderArea->getDisplayDevice();
-
-    std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
-    layers.reserve(layerCount);
-    std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
-    traverseLayers([&](Layer* layer) {
-        captureResults.capturedHdrLayers |= isHdrLayer(layer);
-        // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting
-        // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are
-        // generated with the layer's current buffer and geometry.
-        layer->updateSnapshot(true /* updateGeometry */);
-
-        layers.emplace_back(layer, layer->copyCompositionEngineLayerFE());
-
-        sp<LayerFE>& layerFE = layers.back().second;
-
-        layerFE->mSnapshot->geomLayerTransform =
-                renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
-
-        if (layer->needsFilteringForScreenshots(display.get(), transform)) {
-            filterForScreenshot.insert(layerFE.get());
-        }
-    });
-
     ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
     if (!layers.empty()) {
         const sp<LayerFE>& layerFE = layers.back().second;
@@ -6764,9 +7059,9 @@
     };
 
     auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits,
-                    displayBrightnessNits, filterForScreenshot = std::move(filterForScreenshot),
-                    grayscale, layerFEs = copyLayerFEs(), layerStack, regionSampling,
-                    renderArea = std::move(renderArea), renderIntent]() -> FenceResult {
+                    displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(), layerStack,
+                    regionSampling, renderArea = std::move(renderArea),
+                    renderIntent]() -> FenceResult {
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
         compositionEngine->setRenderEngine(mRenderEngine.get());
@@ -6782,7 +7077,6 @@
                                         .buffer = std::move(buffer),
                                         .sdrWhitePointNits = sdrWhitePointNits,
                                         .displayBrightnessNits = displayBrightnessNits,
-                                        .filterForScreenshot = std::move(filterForScreenshot),
                                         .regionSampling = regionSampling});
 
         const float colorSaturation = grayscale ? 0 : 1;
@@ -7359,24 +7653,18 @@
     return true;
 }
 
-bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId) {
-    std::vector<LayerCreatedState> createdLayers;
-    {
-        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
-        createdLayers = std::move(mCreatedLayers);
-        mCreatedLayers.clear();
-        if (createdLayers.size() == 0) {
-            return false;
-        }
+bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId,
+                                         std::vector<LayerCreatedState>& createdLayers) {
+    if (createdLayers.size() == 0) {
+        return false;
     }
 
     Mutex::Autolock _l(mStateLock);
     for (const auto& createdLayer : createdLayers) {
         handleLayerCreatedLocked(createdLayer, vsyncId);
     }
-    createdLayers.clear();
     mLayersAdded = true;
-    return true;
+    return mLayersAdded;
 }
 
 void SurfaceFlinger::updateLayerMetadataSnapshot() {
@@ -7404,6 +7692,150 @@
     });
 }
 
+void SurfaceFlinger::moveSnapshotsFromCompositionArgs(
+        compositionengine::CompositionRefreshArgs& refreshArgs,
+        std::vector<std::pair<Layer*, LayerFE*>>& layers) {
+    if (mLayerLifecycleManagerEnabled) {
+        std::vector<std::unique_ptr<frontend::LayerSnapshot>>& snapshots =
+                mLayerSnapshotBuilder.getSnapshots();
+        for (auto [_, layerFE] : layers) {
+            auto i = layerFE->mSnapshot->globalZ;
+            snapshots[i] = std::move(layerFE->mSnapshot);
+        }
+    }
+    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+        for (auto [layer, layerFE] : layers) {
+            layer->updateLayerSnapshot(std::move(layerFE->mSnapshot));
+        }
+    }
+}
+
+std::vector<std::pair<Layer*, LayerFE*>> SurfaceFlinger::moveSnapshotsToCompositionArgs(
+        compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, int64_t vsyncId) {
+    std::vector<std::pair<Layer*, LayerFE*>> layers;
+    if (mLayerLifecycleManagerEnabled) {
+        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                    if (cursorOnly &&
+                        snapshot->compositionType !=
+                                aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
+                        return;
+                    }
+
+                    if (!snapshot->hasSomethingToDraw()) {
+                        return;
+                    }
+
+                    auto it = mLegacyLayers.find(snapshot->sequence);
+                    LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(),
+                                        "Couldnt find layer object for %s",
+                                        snapshot->getDebugString().c_str());
+                    auto& legacyLayer = it->second;
+                    sp<LayerFE> layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path);
+                    layerFE->mSnapshot = std::move(snapshot);
+                    refreshArgs.layers.push_back(layerFE);
+                    layers.emplace_back(legacyLayer.get(), layerFE.get());
+                });
+    }
+    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+        mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
+                if (cursorOnly &&
+                    layer->getLayerSnapshot()->compositionType !=
+                            aidl::android::hardware::graphics::composer3::Composition::CURSOR)
+                    return;
+                layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
+                layerFE->mSnapshot = layer->stealLayerSnapshot();
+                refreshArgs.layers.push_back(layerFE);
+                layers.emplace_back(layer, layerFE.get());
+            }
+        });
+    }
+
+    return layers;
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack,
+                                                uint32_t uid) {
+    return [this, layerStack, uid]() {
+        std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+        for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+            if (layerStack && snapshot->outputFilter.layerStack != *layerStack) {
+                continue;
+            }
+            if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) {
+                continue;
+            }
+            if (!snapshot->isVisible || !snapshot->hasSomethingToDraw()) {
+                continue;
+            }
+
+            auto it = mLegacyLayers.find(snapshot->sequence);
+            LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
+                                snapshot->getDebugString().c_str());
+            auto& legacyLayer = it->second;
+            sp<LayerFE> layerFE = getFactory().createLayerFE(legacyLayer->getName());
+            layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot);
+            layers.emplace_back(legacyLayer.get(), std::move(layerFE));
+        }
+
+        return layers;
+    };
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid,
+                                                std::unordered_set<uint32_t> excludeLayerIds,
+                                                bool childrenOnly, const FloatRect& parentCrop) {
+    return [this, excludeLayerIds = std::move(excludeLayerIds), uid, rootLayerId, childrenOnly,
+            parentCrop]() {
+        frontend::LayerSnapshotBuilder::Args
+                args{.root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly),
+                     .layerLifecycleManager = mLayerLifecycleManager,
+                     .displays = mFrontEndDisplayInfos,
+                     .displayChanges = true,
+                     .globalShadowSettings = mDrawingState.globalShadowSettings,
+                     .supportsBlur = mSupportsBlur,
+                     .forceFullDamage = mForceFullDamage,
+                     .parentCrop = {parentCrop},
+                     .excludeLayerIds = std::move(excludeLayerIds)};
+        mLayerSnapshotBuilder.update(args);
+
+        auto getLayerSnapshotsFn = getLayerSnapshotsForScreenshots({}, uid);
+        std::vector<std::pair<Layer*, sp<LayerFE>>> layers = getLayerSnapshotsFn();
+        args.root = mLayerHierarchyBuilder.getHierarchy();
+        args.parentCrop.reset();
+        args.excludeLayerIds.clear();
+        mLayerSnapshotBuilder.update(args);
+        return layers;
+    };
+}
+
+SurfaceFlinger::LifecycleUpdate SurfaceFlinger::flushLifecycleUpdates() {
+    LifecycleUpdate update;
+    ATRACE_NAME("TransactionHandler:flushTransactions");
+    // Locking:
+    // 1. to prevent onHandleDestroyed from being called while the state lock is held,
+    // we must keep a copy of the transactions (specifically the composer
+    // states) around outside the scope of the lock.
+    // 2. Transactions and created layers do not share a lock. To prevent applying
+    // transactions with layers still in the createdLayer queue, flush the transactions
+    // before committing the created layers.
+    update.transactions = mTransactionHandler.flushTransactions();
+    {
+        // TODO(b/238781169) lockless queue this and keep order.
+        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+        update.layerCreatedStates = std::move(mCreatedLayers);
+        mCreatedLayers.clear();
+        update.newLayers = std::move(mNewLayers);
+        mNewLayers.clear();
+        update.destroyedHandles = std::move(mDestroyedHandles);
+        mDestroyedHandles.clear();
+    }
+    return update;
+}
+
 // gui::ISurfaceComposer
 
 binder::Status SurfaceComposerAIDL::bootFinished() {
@@ -7417,9 +7849,9 @@
 
 binder::Status SurfaceComposerAIDL::createDisplayEventConnection(
         VsyncSource vsyncSource, EventRegistration eventRegistration,
-        sp<IDisplayEventConnection>* outConnection) {
+        const sp<IBinder>& layerHandle, sp<IDisplayEventConnection>* outConnection) {
     sp<IDisplayEventConnection> conn =
-            mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration);
+            mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration, layerHandle);
     if (conn == nullptr) {
         *outConnection = nullptr;
         return binderStatusFromStatusT(BAD_VALUE);
@@ -7441,13 +7873,14 @@
 }
 
 binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure,
+                                                  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);
+    *outDisplay = mFlinger->createDisplay(displayName8, secure, requestedRefreshRate);
     return binder::Status::ok();
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9601f2e..4171d39 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -57,6 +57,8 @@
 #include <scheduler/PresentLatencyTracker.h>
 #include <scheduler/Time.h>
 #include <scheduler/TransactionSchedule.h>
+#include <scheduler/interface/CompositionCoverage.h>
+#include <scheduler/interface/ICompositor.h>
 #include <ui/FenceResult.h>
 
 #include "Display/DisplayMap.h"
@@ -69,8 +71,12 @@
 #include "FlagManager.h"
 #include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerSnapshot.h"
+#include "FrontEnd/LayerSnapshotBuilder.h"
 #include "FrontEnd/TransactionHandler.h"
 #include "LayerVector.h"
+#include "Scheduler/ISchedulerCallback.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "Scheduler/RefreshRateStats.h"
 #include "Scheduler/Scheduler.h"
@@ -99,6 +105,7 @@
 #include <vector>
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
 #include "Client.h"
 
 using namespace android::surfaceflinger;
@@ -122,6 +129,7 @@
 class ScreenCapturer;
 class WindowInfosListenerInvoker;
 
+using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using frontend::TransactionHandler;
 using gui::CaptureArgs;
 using gui::DisplayCaptureArgs;
@@ -346,6 +354,7 @@
     friend class LayerRenderArea;
     friend class LayerTracing;
     friend class SurfaceComposerAIDL;
+    friend class DisplayRenderArea;
 
     // For unit tests
     friend class TestableSurfaceFlinger;
@@ -353,7 +362,7 @@
     friend class TunnelModeEnabledReporterTest;
 
     using TransactionSchedule = scheduler::TransactionSchedule;
-    using TraverseLayersFunction = std::function<void(const LayerVector::Visitor&)>;
+    using GetLayerSnapshotsFunction = std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>;
     using RenderAreaFuture = ftl::Future<std::unique_ptr<RenderArea>>;
     using DumpArgs = Vector<String16>;
     using Dumper = std::function<void(const DumpArgs&, bool asProto, std::string&)>;
@@ -445,6 +454,26 @@
         FINISHED,
     };
 
+    struct LayerCreatedState {
+        LayerCreatedState(const wp<Layer>& layer, const wp<Layer>& parent, bool addToRoot)
+              : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
+        wp<Layer> layer;
+        // Indicates the initial parent of the created layer, only used for creating layer in
+        // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
+        wp<Layer> initialParent;
+        // Indicates whether the layer getting created should be added at root if there's no parent
+        // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
+        // be added offscreen.
+        bool addToRoot;
+    };
+
+    struct LifecycleUpdate {
+        std::vector<TransactionState> transactions;
+        std::vector<LayerCreatedState> layerCreatedStates;
+        std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
+        std::vector<uint32_t> destroyedHandles;
+    };
+
     template <typename F, std::enable_if_t<!std::is_member_function_pointer_v<F>>* = nullptr>
     static Dumper dumper(F&& dump) {
         using namespace std::placeholders;
@@ -481,7 +510,8 @@
             EXCLUDES(mStateLock);
 
     // Implements ISurfaceComposer
-    sp<IBinder> createDisplay(const String8& displayName, bool secure);
+    sp<IBinder> createDisplay(const String8& displayName, bool secure,
+                              float requestedRefreshRate = 0);
     void destroyDisplay(const sp<IBinder>& displayToken);
     std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const EXCLUDES(mStateLock) {
         Mutex::Autolock lock(mStateLock);
@@ -494,8 +524,7 @@
                                  uint32_t flags, const sp<IBinder>& applyToken,
                                  const InputWindowCommands& inputWindowCommands,
                                  int64_t desiredPresentTime, bool isAutoTimestamp,
-                                 const std::vector<client_cache_t>& uncacheBuffers,
-                                 bool hasListenerCallbacks,
+                                 const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
                                  const std::vector<ListenerCallbacks>& listenerCallbacks,
                                  uint64_t transactionId) override;
     void bootFinished();
@@ -503,7 +532,8 @@
     sp<IDisplayEventConnection> createDisplayEventConnection(
             gui::ISurfaceComposer::VsyncSource vsyncSource =
                     gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
-            EventRegistrationFlags eventRegistration = {});
+            EventRegistrationFlags eventRegistration = {},
+            const sp<IBinder>& layerHandle = nullptr);
 
     status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
     status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
@@ -602,21 +632,12 @@
                                                const hal::VsyncPeriodChangeTimeline&) override;
     void onComposerHalSeamlessPossible(hal::HWDisplayId) override;
     void onComposerHalVsyncIdle(hal::HWDisplayId) override;
+    void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) override;
 
     // ICompositor overrides:
-
-    // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL.
     void configure() override;
-
-    // Commits transactions for layers and displays. Returns whether any state has been invalidated,
-    // i.e. whether a frame should be composited for each display.
     bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) override;
-
-    // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
-    // via RenderEngine and the Composer HAL, respectively.
     void composite(TimePoint frameTime, VsyncId) override;
-
-    // Samples the composited frame via RegionSamplingThread.
     void sample() override;
 
     // ISchedulerCallback overrides:
@@ -692,6 +713,17 @@
 
     void updateLayerGeometry();
     void updateLayerMetadataSnapshot();
+    std::vector<std::pair<Layer*, LayerFE*>> moveSnapshotsToCompositionArgs(
+            compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly,
+            int64_t vsyncId);
+    void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
+                                          std::vector<std::pair<Layer*, LayerFE*>>& layers);
+    bool updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update,
+                                    bool transactionsFlushed, bool& out)
+            REQUIRES(kMainThreadContext);
+    bool updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, bool transactionsFlushed,
+                              bool& out) REQUIRES(kMainThreadContext);
+    LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
     void updateInputFlinger();
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
@@ -706,19 +738,23 @@
     /*
      * Transactions
      */
-    bool applyTransactionState(
-            const FrameTimelineInfo& info, std::vector<ResolvedComposerState>& state,
-            Vector<DisplayState>& displays, uint32_t flags,
-            const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime,
-            bool isAutoTimestamp, const std::vector<uint64_t>& uncacheBufferIds,
-            const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks,
-            const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid,
-            uint64_t transactionId) REQUIRES(mStateLock);
+    bool applyTransactionState(const FrameTimelineInfo& info,
+                               std::vector<ResolvedComposerState>& state,
+                               Vector<DisplayState>& displays, uint32_t flags,
+                               const InputWindowCommands& inputWindowCommands,
+                               const int64_t desiredPresentTime, bool isAutoTimestamp,
+                               const client_cache_t& uncacheBuffer, const int64_t postTime,
+                               uint32_t permissions, bool hasListenerCallbacks,
+                               const std::vector<ListenerCallbacks>& listenerCallbacks,
+                               int originPid, int originUid, uint64_t transactionId)
+            REQUIRES(mStateLock);
     // Flush pending transactions that were presented after desiredPresentTime.
     // For test only
     bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext);
 
     bool applyTransactions(std::vector<TransactionState>&, VsyncId) REQUIRES(kMainThreadContext);
+    bool applyAndCommitDisplayTransactionStates(std::vector<TransactionState>& transactions)
+            REQUIRES(kMainThreadContext);
 
     // Returns true if there is at least one transaction that needs to be flushed
     bool transactionFlushNeeded();
@@ -734,7 +770,10 @@
                                   int64_t desiredPresentTime, bool isAutoTimestamp,
                                   int64_t postTime, uint32_t permissions, uint64_t transactionId)
             REQUIRES(mStateLock);
-
+    uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&,
+                                          int64_t desiredPresentTime, bool isAutoTimestamp,
+                                          int64_t postTime, uint32_t permissions,
+                                          uint64_t transactionId) REQUIRES(mStateLock);
     uint32_t getTransactionFlags() const;
 
     // Sets the masked bits, and schedules a commit if needed.
@@ -787,16 +826,16 @@
     // Boot animation, on/off animations and screen capture
     void startBootAnim();
 
-    ftl::SharedFuture<FenceResult> captureScreenCommon(RenderAreaFuture, TraverseLayersFunction,
+    ftl::SharedFuture<FenceResult> captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction,
                                                        ui::Size bufferSize, ui::PixelFormat,
                                                        bool allowProtected, bool grayscale,
                                                        const sp<IScreenCaptureListener>&);
     ftl::SharedFuture<FenceResult> captureScreenCommon(
-            RenderAreaFuture, TraverseLayersFunction,
+            RenderAreaFuture, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, const sp<IScreenCaptureListener>&);
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            std::shared_ptr<const RenderArea>, TraverseLayersFunction,
+            std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
             bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock)
             REQUIRES(kMainThreadContext);
@@ -892,7 +931,7 @@
 
     // mark a region of a layer stack dirty. this updates the dirty
     // region of all screens presenting this layer stack.
-    void invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty);
+    void invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty);
 
     ui::LayerFilter makeLayerFilterForDisplay(DisplayId displayId, ui::LayerStack layerStack)
             REQUIRES(mStateLock) {
@@ -958,9 +997,9 @@
      */
     nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock);
 
-    void setHWCVsyncEnabled(PhysicalDisplayId id, hal::Vsync enabled) {
-        mLastHWCVsyncState = enabled;
-        getHwComposer().setVsyncEnabled(id, enabled);
+    void setHWCVsyncEnabled(PhysicalDisplayId id, bool enabled) {
+        hal::Vsync halState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
+        getHwComposer().setVsyncEnabled(id, halState);
     }
 
     using FenceTimePtr = std::shared_ptr<FenceTime>;
@@ -1085,7 +1124,7 @@
     void updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& activeDisplay)
             REQUIRES(mStateLock, kMainThreadContext);
 
-    bool isHdrLayer(Layer* layer) const;
+    bool isHdrLayer(const frontend::LayerSnapshot& snapshot) const;
 
     ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
             REQUIRES(mStateLock);
@@ -1133,9 +1172,8 @@
     State mDrawingState{LayerVector::StateSet::Drawing};
     bool mVisibleRegionsDirty = false;
 
-    // VisibleRegions dirty is already cleared by postComp, but we need to track it to prevent
-    // extra work in the HDR layer info listener.
-    bool mVisibleRegionsWereDirtyThisFrame = false;
+    bool mHdrLayerInfoChanged = false;
+
     // Used to ensure we omit a callback when HDR layer info listener is newly added but the
     // scene hasn't changed
     bool mAddingHDRLayerInfoListener = false;
@@ -1146,29 +1184,18 @@
     // TODO: Also move visibleRegions over to a boolean system.
     bool mUpdateInputInfo = false;
     bool mSomeChildrenChanged;
-    bool mSomeDataspaceChanged = false;
     bool mForceTransactionDisplayChange = false;
 
     // Set if LayerMetadata has changed since the last LayerMetadata snapshot.
     bool mLayerMetadataSnapshotNeeded = false;
 
+    // TODO(b/238781169) validate these on composition
     // Tracks layers that have pending frames which are candidates for being
     // latched.
     std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithQueuedFrames;
     // Tracks layers that need to update a display's dirty region.
     std::vector<sp<Layer>> mLayersPendingRefresh;
 
-    // True if in the previous frame at least one layer was composed via the GPU.
-    bool mHadClientComposition = false;
-    // True if in the previous frame at least one layer was composed via HW Composer.
-    // Note that it is possible for a frame to be composed via both client and device
-    // composition, for example in the case of overlays.
-    bool mHadDeviceComposition = false;
-    // True if in the previous frame, the client composition was skipped by reusing the buffer
-    // used in a previous composition. This can happed if the client composition requests
-    // did not change.
-    bool mReusedClientComposition = false;
-
     BootStage mBootStage = BootStage::BOOTLOADER;
 
     struct HotplugEvent {
@@ -1204,7 +1231,7 @@
     std::atomic_bool mForceFullDamage = false;
 
     bool mLayerCachingEnabled = false;
-    bool mPropagateBackpressureClientComposition = false;
+    bool mBackpressureGpuComposition = false;
 
     LayerTracing mLayerTracing{*this};
     bool mLayerTracingEnabled = false;
@@ -1268,6 +1295,9 @@
     std::atomic<int> mNumTrustedPresentationListeners = 0;
 
     std::unique_ptr<compositionengine::CompositionEngine> mCompositionEngine;
+
+    CompositionCoverageFlags mCompositionCoverage;
+
     // mMaxRenderTargetSize is only set once in init() so it doesn't need to be protected by
     // any mutex.
     size_t mMaxRenderTargetSize{1};
@@ -1296,9 +1326,6 @@
     TimePoint mScheduledPresentTime GUARDED_BY(kMainThreadContext);
     TimePoint mExpectedPresentTime GUARDED_BY(kMainThreadContext);
 
-    hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE;
-    hal::Vsync mLastHWCVsyncState = hal::Vsync::DISABLE;
-
     // below flags are set by main thread only
     bool mSetActiveModePending = false;
 
@@ -1332,24 +1359,13 @@
 
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
             GUARDED_BY(mStateLock);
+
     mutable std::mutex mCreatedLayersLock;
-    struct LayerCreatedState {
-        LayerCreatedState(const wp<Layer>& layer, const wp<Layer> parent, bool addToRoot)
-              : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
-        wp<Layer> layer;
-        // Indicates the initial parent of the created layer, only used for creating layer in
-        // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
-        wp<Layer> initialParent;
-        // Indicates whether the layer getting created should be added at root if there's no parent
-        // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
-        // be added offscreen.
-        bool addToRoot;
-    };
 
     // A temporay pool that store the created layers and will be added to current state in main
     // thread.
     std::vector<LayerCreatedState> mCreatedLayers GUARDED_BY(mCreatedLayersLock);
-    bool commitCreatedLayers(VsyncId);
+    bool commitCreatedLayers(VsyncId, std::vector<LayerCreatedState>& createdLayers);
     void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock);
 
     mutable std::mutex mMirrorDisplayLock;
@@ -1371,6 +1387,11 @@
         return hasDisplay(
                 [](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
     }
+    std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+            std::optional<ui::LayerStack> layerStack, uint32_t uid);
+    std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+            uint32_t rootLayerId, uint32_t uid, std::unordered_set<uint32_t> excludeLayerIds,
+            bool childrenOnly, const FloatRect& parentCrop);
 
     const sp<WindowInfosListenerInvoker> mWindowInfosListenerInvoker;
 
@@ -1383,6 +1404,18 @@
 
     bool mPowerHintSessionEnabled;
 
+    bool mLayerLifecycleManagerEnabled = false;
+    bool mLegacyFrontEndEnabled = true;
+
+    frontend::LayerLifecycleManager mLayerLifecycleManager;
+    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+    frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
+
+    std::vector<uint32_t> mDestroyedHandles;
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
+    // These classes do not store any client state but help with managing transaction callbacks
+    // and stats.
+    std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers;
     struct {
         bool late = false;
         bool early = false;
@@ -1390,6 +1423,7 @@
 
     TransactionHandler mTransactionHandler;
     display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
+    bool mFrontEndDisplayInfosChanged = false;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
@@ -1399,10 +1433,11 @@
     binder::Status bootFinished() override;
     binder::Status createDisplayEventConnection(
             VsyncSource vsyncSource, EventRegistration eventRegistration,
+            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,
-                                 sp<IBinder>* outDisplay) override;
+                                 float requestedRefreshRate, sp<IBinder>* outDisplay) override;
     binder::Status destroyDisplay(const sp<IBinder>& display) override;
     binder::Status getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) override;
     binder::Status getPhysicalDisplayToken(int64_t displayId, sp<IBinder>* outDisplay) override;
@@ -1487,6 +1522,7 @@
     binder::Status removeHdrLayerInfoListener(
             const sp<IBinder>& displayToken,
             const sp<gui::IHdrLayerInfoListener>& listener) override;
+
     binder::Status notifyPowerBoost(int boostId) override;
     binder::Status setGlobalShadowSettings(const gui::Color& ambientColor,
                                            const gui::Color& spotColor, float lightPosY,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index e5de759..3587a72 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -92,11 +92,6 @@
     return NO_ERROR;
 }
 
-status_t TransactionCallbackInvoker::registerUnpresentedCallbackHandle(
-        const sp<CallbackHandle>& handle) {
-    return addCallbackHandle(handle, std::vector<JankData>());
-}
-
 status_t TransactionCallbackInvoker::findOrCreateTransactionStats(
         const sp<IBinder>& listener, const std::vector<CallbackId>& callbackIds,
         TransactionStats** outTransactionStats) {
@@ -137,7 +132,6 @@
             sp<Fence> currentFence = future.get().value_or(Fence::NO_FENCE);
             if (prevFence == nullptr && currentFence->getStatus() != Fence::Status::Invalid) {
                 prevFence = std::move(currentFence);
-                handle->previousReleaseFence = prevFence;
             } else if (prevFence != nullptr) {
                 // If both fences are signaled or both are unsignaled, we need to merge
                 // them to get an accurate timestamp.
@@ -147,8 +141,7 @@
                     snprintf(fenceName, 32, "%.28s", handle->name.c_str());
                     sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, currentFence);
                     if (mergedFence->isValid()) {
-                        handle->previousReleaseFence = std::move(mergedFence);
-                        prevFence = handle->previousReleaseFence;
+                        prevFence = std::move(mergedFence);
                     }
                 } else if (currentFence->getStatus() == Fence::Status::Unsignaled) {
                     // If one fence has signaled and the other hasn't, the unsignaled
@@ -158,10 +151,11 @@
                     // 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.
-                    handle->previousReleaseFence = std::move(currentFence);
+                    prevFence = std::move(currentFence);
                 }
             }
         }
+        handle->previousReleaseFence = prevFence;
         handle->previousReleaseFences.clear();
 
         FrameEventHistoryStats eventStats(handle->frameNumber,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 61ff9bc..3074795 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -66,9 +66,6 @@
     status_t addOnCommitCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
                                              std::deque<sp<CallbackHandle>>& outRemainingHandles);
 
-    // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and
-    // presented this frame.
-    status_t registerUnpresentedCallbackHandle(const sp<CallbackHandle>& handle);
     void addEmptyTransaction(const ListenerCallbacks& listenerCallbacks);
 
     void addPresentFence(sp<Fence>);
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 5025c49..aeca56e 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -27,6 +27,12 @@
 
 namespace android {
 
+enum TraverseBuffersReturnValues {
+    CONTINUE_TRAVERSAL,
+    STOP_TRAVERSAL,
+    DELETE_AND_CONTINUE_TRAVERSAL,
+};
+
 // Extends the client side composer state by resolving buffer.
 class ResolvedComposerState : public ComposerState {
 public:
@@ -43,7 +49,7 @@
                      const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
                      const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
                      int64_t desiredPresentTime, bool isAutoTimestamp,
-                     std::vector<uint64_t> uncacheBufferIds, int64_t postTime, uint32_t permissions,
+                     const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions,
                      bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
                      int originPid, int originUid, uint64_t transactionId)
           : frameTimelineInfo(frameTimelineInfo),
@@ -54,7 +60,7 @@
             inputWindowCommands(inputWindowCommands),
             desiredPresentTime(desiredPresentTime),
             isAutoTimestamp(isAutoTimestamp),
-            uncacheBufferIds(std::move(uncacheBufferIds)),
+            buffer(uncacheBuffer),
             postTime(postTime),
             permissions(permissions),
             hasListenerCallbacks(hasListenerCallbacks),
@@ -75,12 +81,18 @@
     }
 
     template <typename Visitor>
-    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) const {
-        for (const auto& state : states) {
-            if (state.state.hasBufferChanges() && state.state.hasValidBuffer() &&
-                state.state.surface) {
-                if (!visitor(state.state)) return;
+    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) {
+        for (auto state = states.begin(); state != states.end();) {
+            if (state->state.hasBufferChanges() && state->state.hasValidBuffer() &&
+                state->state.surface) {
+                int result = visitor(state->state);
+                if (result == STOP_TRAVERSAL) return;
+                if (result == DELETE_AND_CONTINUE_TRAVERSAL) {
+                    state = states.erase(state);
+                    continue;
+                }
             }
+            state++;
         }
     }
 
@@ -109,7 +121,7 @@
     InputWindowCommands inputWindowCommands;
     int64_t desiredPresentTime;
     bool isAutoTimestamp;
-    std::vector<uint64_t> uncacheBufferIds;
+    client_cache_t buffer;
     int64_t postTime;
     uint32_t permissions;
     bool hasListenerCallbacks;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
index 6a6e3db..1a951b3 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
@@ -41,6 +41,7 @@
 
 namespace android::hardware::graphics::composer::hal {
 
+using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using ::android::hardware::Return;
 using ::android::hardware::Void;
 using ::android::HWC2::ComposerCallback;
@@ -99,6 +100,7 @@
     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_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index cdffbb4..7695459 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -234,7 +234,8 @@
                       std::shared_ptr<RefreshRateSelector> selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
-        mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
+        mVsyncSchedule = std::unique_ptr<VsyncSchedule>(
+                new VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
 
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr));
@@ -244,9 +245,6 @@
         return Scheduler::createConnection(std::move(eventThread));
     }
 
-    auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
-    auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
-
     auto &mutableLayerHistory() { return mLayerHistory; }
 
     auto refreshRateSelector() { return leaderSelectorPtr(); }
@@ -739,18 +737,17 @@
         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,
+    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,
+                             const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
+                             std::vector<ListenerCallbacks> &listenerCallbacks,
                              uint64_t transactionId) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
-                                             isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
+                                             isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
                                              listenerCallbacks, transactionId);
     }
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index acfc1d4..c088e7b 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -148,7 +148,7 @@
     layer->fenceHasSignaled();
     layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
     const std::vector<sp<CallbackHandle>> callbacks;
-    layer->setTransactionCompletedListeners(callbacks);
+    layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool());
 
     std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
             renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral<uint32_t>(),
@@ -166,7 +166,7 @@
                               {mFdp.ConsumeIntegral<int32_t>(),
                                mFdp.ConsumeIntegral<int32_t>()} /*reqSize*/,
                               mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(),
-                              getFuzzedRect(), mFdp.ConsumeBool());
+                              mFdp.ConsumeBool());
     layerArea.render([]() {} /*drawLayers*/);
 
     if (!ownsHandle) {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 44805db..61fb29a 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -76,7 +76,7 @@
 
     FuzzedDataProvider mFdp;
 
-    std::optional<scheduler::VsyncSchedule> mVsyncSchedule;
+    std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
 };
 
 PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
@@ -90,9 +90,9 @@
 }
 
 void SchedulerFuzzer::fuzzEventThread() {
-    mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
-                                                    std::make_unique<mock::VSyncDispatch>(),
-                                                    nullptr));
+    mVsyncSchedule = std::unique_ptr<scheduler::VsyncSchedule>(
+            new scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
+                                         std::make_unique<mock::VSyncDispatch>(), nullptr));
     const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
     std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
             android::impl::EventThread>("fuzzer", *mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index fedd71e..0495678 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -139,11 +139,6 @@
 
     set_sched_policy(0, SP_FOREGROUND);
 
-    // Put most SurfaceFlinger threads in the system-background cpuset
-    // Keeps us from unnecessarily using big cores
-    // Do this after the binder thread pool init
-    if (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);
-
     // initialize before clients can connect
     flinger->init();
 
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index de47330..6d12aa7 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -57,6 +57,7 @@
         "SetFrameRateOverride_test.cpp",
         "SetGeometry_test.cpp",
         "Stress_test.cpp",
+        "TextureFiltering_test.cpp",
         "VirtualDisplay_test.cpp",
         "WindowInfosListener_test.cpp",
     ],
diff --git a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
index 16076ea..c23fb9b 100644
--- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
+++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
@@ -85,7 +85,8 @@
                              sp<Fence> fence, CallbackHelper& callback, const ReleaseCallbackId& id,
                              ReleaseBufferCallbackHelper& releaseCallback) {
         Transaction t;
-        t.setBuffer(layer, buffer, fence, id.framenumber, releaseCallback.getCallback());
+        t.setBuffer(layer, buffer, fence, id.framenumber, 0 /* producerId */,
+                    releaseCallback.getCallback());
         t.addTransactionCompletedCallback(callback.function, callback.getContext());
         t.apply();
     }
@@ -301,7 +302,7 @@
 
     Transaction t;
     t.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
-                releaseCallback->getCallback());
+                0 /* producerId */, releaseCallback->getCallback());
     t.addTransactionCompletedCallback(transactionCallback.function,
                                       transactionCallback.getContext());
     t.setDesiredPresentTime(time);
@@ -317,7 +318,7 @@
     sp<GraphicBuffer> secondBuffer = getBuffer();
     ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
     t.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
-                releaseCallback->getCallback());
+                0 /* producerId */, releaseCallback->getCallback());
     t.addTransactionCompletedCallback(transactionCallback.function,
                                       transactionCallback.getContext());
     t.setDesiredPresentTime(time);
@@ -362,7 +363,7 @@
 
     Transaction transaction1;
     transaction1.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
-                           releaseCallback->getCallback());
+                           0 /* producerId */, releaseCallback->getCallback());
     transaction1.addTransactionCompletedCallback(callback1.function, callback1.getContext());
 
     // Set a different TransactionCompletedListener to mimic a second process
@@ -397,14 +398,14 @@
     // Create transaction with a buffer.
     Transaction transaction;
     transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
-                          releaseCallback->getCallback());
+                          0 /* producerId */, releaseCallback->getCallback());
 
     sp<GraphicBuffer> secondBuffer = getBuffer();
     ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
 
     // Call setBuffer on the same transaction with a different buffer.
     transaction.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
-                          releaseCallback->getCallback());
+                          0 /* producerId */, releaseCallback->getCallback());
 
     ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
 }
@@ -419,7 +420,7 @@
     // Create transaction with a buffer.
     Transaction transaction1;
     transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
-                           releaseCallback->getCallback());
+                           0 /* producerId */, releaseCallback->getCallback());
 
     sp<GraphicBuffer> secondBuffer = getBuffer();
     ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
@@ -427,7 +428,7 @@
     // Create a second transaction with a new buffer for the same layer.
     Transaction transaction2;
     transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
-                           releaseCallback->getCallback());
+                           0 /* producerId */, releaseCallback->getCallback());
 
     // merge transaction1 into transaction2 so ensure we get a proper buffer release callback.
     transaction1.merge(std::move(transaction2));
@@ -450,7 +451,7 @@
 
     Transaction transaction1;
     transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
-                           releaseCallback->getCallback());
+                           0 /* producerId */, releaseCallback->getCallback());
 
     // Sent a second buffer to allow the first buffer to get released.
     sp<GraphicBuffer> secondBuffer = getBuffer();
@@ -458,7 +459,7 @@
 
     Transaction transaction2;
     transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
-                           releaseCallback->getCallback());
+                           0 /* producerId */, releaseCallback->getCallback());
 
     // Set a different TransactionCompletedListener to mimic a second process
     TransactionCompletedListener::setInstance(secondCompletedListener);
@@ -479,10 +480,11 @@
     // Create transaction with a buffer.
     Transaction transaction;
     transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
-                          releaseCallback->getCallback());
+                          0 /* producerId */, releaseCallback->getCallback());
 
     // Call setBuffer on the same transaction with a null buffer.
-    transaction.setBuffer(layer, nullptr, std::nullopt, 0, releaseCallback->getCallback());
+    transaction.setBuffer(layer, nullptr, std::nullopt, 0, 0 /* producerId */,
+                          releaseCallback->getCallback());
 
     ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
 }
diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp
new file mode 100644
index 0000000..e9b1fbb
--- /dev/null
+++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/gui/ISurfaceComposerClient.h>
+#include <gtest/gtest.h>
+#include <gui/DisplayCaptureArgs.h>
+#include <ui/GraphicTypes.h>
+#include <ui/Rect.h>
+
+#include "LayerTransactionTest.h"
+
+namespace android {
+
+bool operator==(const Color& left, const Color& right) {
+    return left.a == right.a && left.r == right.r && left.g == right.g && left.b == right.b;
+}
+
+class TextureFilteringTest : public LayerTransactionTest {
+protected:
+    virtual void SetUp() {
+        LayerTransactionTest::SetUp();
+
+        mParent = createLayer("test-parent", 100, 100,
+                              gui::ISurfaceComposerClient::eFXSurfaceContainer);
+        mLayer = createLayer("test-child", 100, 100,
+                             gui::ISurfaceComposerClient::eFXSurfaceBufferState, mParent.get());
+        sp<GraphicBuffer> buffer =
+                sp<GraphicBuffer>::make(static_cast<uint32_t>(100), static_cast<uint32_t>(100),
+                                        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, 50, 100}, Color::RED);
+        TransactionUtils::fillGraphicBufferColor(buffer, Rect{50, 0, 100, 100}, Color::BLUE);
+        Transaction()
+                .setBuffer(mLayer, buffer)
+                .setDataspace(mLayer, ui::Dataspace::V0_SRGB)
+                .setLayer(mLayer, INT32_MAX)
+                .apply();
+    }
+
+    virtual void TearDown() { LayerTransactionTest::TearDown(); }
+
+    void expectFiltered(Rect redRect, Rect blueRect) {
+        // Check that at least some of the pixels in the red rectangle aren't solid red
+        int redPixels = 0;
+        for (int x = redRect.left; x < redRect.right; x++) {
+            for (int y = redRect.top; y < redRect.bottom; y++) {
+                redPixels += mCapture->getPixelColor(static_cast<uint32_t>(x),
+                                                     static_cast<uint32_t>(y)) == Color::RED;
+            }
+        }
+        ASSERT_LT(redPixels, redRect.getWidth() * redRect.getHeight());
+
+        // Check that at least some of the pixels in the blue rectangle aren't solid blue
+        int bluePixels = 0;
+        for (int x = blueRect.left; x < blueRect.right; x++) {
+            for (int y = blueRect.top; y < blueRect.bottom; y++) {
+                bluePixels += mCapture->getPixelColor(static_cast<uint32_t>(x),
+                                                      static_cast<uint32_t>(y)) == Color::BLUE;
+            }
+        }
+        ASSERT_LT(bluePixels, blueRect.getWidth() * blueRect.getHeight());
+    }
+
+    sp<SurfaceControl> mParent;
+    sp<SurfaceControl> mLayer;
+    std::unique_ptr<ScreenCapture> mCapture;
+};
+
+TEST_F(TextureFilteringTest, NoFiltering) {
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    captureArgs.width = 100;
+    captureArgs.height = 100;
+    captureArgs.sourceCrop = Rect{100, 100};
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
+    mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
+}
+
+TEST_F(TextureFilteringTest, BufferCropNoFiltering) {
+    Transaction().setBufferCrop(mLayer, Rect{0, 0, 100, 100}).apply();
+
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    captureArgs.width = 100;
+    captureArgs.height = 100;
+    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
+    mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
+}
+
+// Expect filtering because the buffer is stretched to the layer's bounds.
+TEST_F(TextureFilteringTest, BufferCropIsFiltered) {
+    Transaction().setBufferCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    captureArgs.width = 100;
+    captureArgs.height = 100;
+    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
+}
+
+// Expect filtering because the output source crop is stretched to the output buffer's size.
+TEST_F(TextureFilteringTest, OutputSourceCropIsFiltered) {
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    captureArgs.width = 100;
+    captureArgs.height = 100;
+    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
+}
+
+// Expect filtering because the layer crop and output source crop are stretched to the output
+// buffer's size.
+TEST_F(TextureFilteringTest, LayerCropOutputSourceCropIsFiltered) {
+    Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    captureArgs.width = 100;
+    captureArgs.height = 100;
+    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
+}
+
+// Expect filtering because the layer is scaled up.
+TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) {
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = mLayer->getHandle();
+    captureArgs.frameScaleX = 2;
+    captureArgs.frameScaleY = 2;
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+    expectFiltered({0, 0, 100, 200}, {100, 0, 200, 200});
+}
+
+// Expect no filtering because the output buffer's size matches the source crop.
+TEST_F(TextureFilteringTest, LayerCaptureOutputSourceCropNoFiltering) {
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = mLayer->getHandle();
+    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
+    mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
+}
+
+// Expect no filtering because the output buffer's size matches the source crop (with a cropped
+// layer).
+TEST_F(TextureFilteringTest, LayerCaptureWithCropNoFiltering) {
+    Transaction().setCrop(mLayer, Rect{10, 10, 90, 90}).apply();
+
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = mLayer->getHandle();
+    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
+    mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
+}
+
+// Expect no filtering because the output source crop and output buffer are the same size.
+TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) {
+    // Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    captureArgs.width = 50;
+    captureArgs.height = 50;
+    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
+    mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
+}
+
+// Expect no filtering because the layer crop shouldn't scale the layer.
+TEST_F(TextureFilteringTest, LayerCropDisplayFrameMatchNoFiltering) {
+    Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
+    mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
+}
+
+// Expect no filtering because the parent layer crop shouldn't scale the layer.
+TEST_F(TextureFilteringTest, ParentCropNoFiltering) {
+    Transaction().setCrop(mParent, Rect{25, 25, 75, 75}).apply();
+
+    gui::DisplayCaptureArgs captureArgs;
+    captureArgs.displayToken = mDisplay;
+    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+
+    mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
+    mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index ba77600..0416e93 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -238,6 +238,8 @@
                                                    CaptureArgs::UNSET_UID, visitor);
     };
 
+    auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+
     const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
             GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
     mCaptureScreenBuffer =
@@ -246,7 +248,7 @@
                                                                       HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                                                       usage);
 
-    auto future = mFlinger.renderScreenImpl(std::move(renderArea), traverseLayers,
+    auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots,
                                             mCaptureScreenBuffer, forSystem, regionSampling);
     ASSERT_TRUE(future.valid());
     const auto fenceResult = future.get();
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index b3aba37..f6bcadc 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -125,7 +125,7 @@
     ConnectionEventRecorder mConnectionEventCallRecorder{0};
     ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
 
-    std::optional<scheduler::VsyncSchedule> mVsyncSchedule;
+    std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
     std::unique_ptr<impl::EventThread> mThread;
     sp<MockEventThreadConnection> mConnection;
     sp<MockEventThreadConnection> mThrottledConnection;
@@ -140,9 +140,9 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
-                                                    std::make_unique<mock::VSyncDispatch>(),
-                                                    nullptr));
+    mVsyncSchedule = std::unique_ptr<scheduler::VsyncSchedule>(
+            new scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
+                                         std::make_unique<mock::VSyncDispatch>(), nullptr));
 
     mock::VSyncDispatch& mockDispatch =
             *static_cast<mock::VSyncDispatch*>(&mVsyncSchedule->getDispatch());
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index f47ac6d..abd7789 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -44,6 +44,17 @@
 
 namespace android::frametimeline {
 
+static const std::string sLayerNameOne = "layer1";
+static const std::string sLayerNameTwo = "layer2";
+
+constexpr const uid_t sUidOne = 0;
+constexpr pid_t sPidOne = 10;
+constexpr pid_t sPidTwo = 20;
+constexpr int32_t sInputEventId = 5;
+constexpr int32_t sLayerIdOne = 1;
+constexpr int32_t sLayerIdTwo = 2;
+constexpr GameMode sGameMode = GameMode::Unsupported;
+
 class FrameTimelineTest : public testing::Test {
 public:
     FrameTimelineTest() {
@@ -106,6 +117,14 @@
         return packets;
     }
 
+    void addEmptySurfaceFrame() {
+        auto surfaceFrame =
+                mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+                                                           sLayerNameOne, sLayerNameOne,
+                                                           /*isBuffer*/ false, sGameMode);
+        mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame));
+    }
+
     void addEmptyDisplayFrame() {
         auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
         // Trigger a flushPresentFence by calling setSfPresent for the next frame
@@ -168,17 +187,6 @@
                                                                   kStartThreshold};
 };
 
-static const std::string sLayerNameOne = "layer1";
-static const std::string sLayerNameTwo = "layer2";
-
-constexpr const uid_t sUidOne = 0;
-constexpr pid_t sPidOne = 10;
-constexpr pid_t sPidTwo = 20;
-constexpr int32_t sInputEventId = 5;
-constexpr int32_t sLayerIdOne = 1;
-constexpr int32_t sLayerIdTwo = 2;
-constexpr GameMode sGameMode = GameMode::Unsupported;
-
 TEST_F(FrameTimelineTest, tokenManagerRemovesStalePredictions) {
     int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
     EXPECT_EQ(getPredictions().size(), 1u);
@@ -1054,6 +1062,9 @@
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
     tracingSession->StartBlocking();
+
+    // Add an empty surface frame so that display frame would get traced.
+    addEmptySurfaceFrame();
     int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30});
 
     // Set up the display frame
@@ -1135,6 +1146,9 @@
     // Flush the token so that it would expire
     flushTokens();
 
+    // Add an empty surface frame so that display frame would get traced.
+    addEmptySurfaceFrame();
+
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
     mFrameTimeline->setSfPresent(26, presentFence1);
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index afbc57a..da00377 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -52,6 +52,7 @@
 
 using Hwc2::Config;
 
+using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using ::testing::_;
 using ::testing::DoAll;
 using ::testing::ElementsAreArray;
@@ -118,6 +119,34 @@
     }
 }
 
+TEST_F(HWComposerTest, onVsync) {
+    constexpr hal::HWDisplayId kHwcDisplayId = 1;
+    expectHotplugConnect(kHwcDisplayId);
+
+    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+
+    const auto physicalDisplayId = info->id;
+
+    // Deliberately chosen not to match DisplayData.lastPresentTimestamp's
+    // initial value.
+    constexpr nsecs_t kTimestamp = 1;
+    auto displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp);
+    ASSERT_TRUE(displayIdOpt);
+    EXPECT_EQ(physicalDisplayId, displayIdOpt);
+
+    // Attempt to send the same time stamp again.
+    displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp);
+    EXPECT_FALSE(displayIdOpt);
+}
+
+TEST_F(HWComposerTest, onVsyncInvalid) {
+    constexpr hal::HWDisplayId kInvalidHwcDisplayId = 2;
+    constexpr nsecs_t kTimestamp = 1;
+    const auto displayIdOpt = mHwc.onVsync(kInvalidHwcDisplayId, kTimestamp);
+    EXPECT_FALSE(displayIdOpt);
+}
+
 struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
     MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection));
     MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId));
@@ -127,6 +156,7 @@
                  void(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&));
     MOCK_METHOD1(onComposerHalSeamlessPossible, void(hal::HWDisplayId));
     MOCK_METHOD1(onComposerHalVsyncIdle, void(hal::HWDisplayId));
+    MOCK_METHOD(void, onRefreshRateChangedDebug, (const RefreshRateChangedDebugData&), (override));
 };
 
 struct HWComposerSetCallbackTest : HWComposerTest {
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 783df28..763426a 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -641,4 +641,69 @@
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
 }
 
+TEST_F(LayerHierarchyTest, canMirrorDisplay) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2,
+                                      1, 11, 111, 12,  121, 122, 1221, 13,   2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+    expected = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5));
+    setLayerStack(3, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+    expected = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    createRootLayer(4);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expected = {3, 1,  11,  111, 12,  121, 122,  1221, 13, 2, 4,
+                                      1, 11, 111, 12,  121, 122, 1221, 13,   2,  4};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+    expected = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    reparentLayer(1, UNASSIGNED_LAYER_ID);
+    destroyLayerHandle(1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expected = {3, 2, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+    expected = {11, 111, 12, 121, 122, 1221, 13};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 1a82232..852cb91 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -60,6 +60,14 @@
         return args;
     }
 
+    LayerCreationArgs createDisplayMirrorArgs(uint32_t id, ui::LayerStack layerStack) {
+        LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+        args.addToRoot = true;
+        args.parentHandle.clear();
+        args.layerStackToMirror = layerStack;
+        return args;
+    }
+
     std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
         std::vector<uint32_t> layerIds;
         hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
@@ -90,6 +98,15 @@
         mLifecycleManager.addLayers(std::move(layers));
     }
 
+    void createDisplayMirrorLayer(uint32_t id, ui::LayerStack layerStack) {
+        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+        mHandles[id] = handle;
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createDisplayMirrorArgs(/*id=*/id, layerStack)));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
     virtual void createLayer(uint32_t id, uint32_t parentId) {
         sp<LayerHandle> handle = sp<LayerHandle>::make(id);
         mHandles[id] = handle;
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index e124342..aa6a14e 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -306,4 +306,27 @@
     EXPECT_EQ(getSnapshot(1)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::NoVote);
 }
 
+// Display Mirroring Tests
+// tree with 3 levels of children
+// ROOT (DISPLAY 0)
+// ├── 1
+// │   ├── 11
+// │   │   └── 111
+// │   ├── 12 (has skip screenshot flag)
+// │   │   ├── 121
+// │   │   └── 122
+// │   │       └── 1221
+// │   └── 13
+// └── 2
+// ROOT (DISPLAY 1)
+// └── 3 (mirrors display 0)
+TEST_F(LayerSnapshotTest, displayMirrorRespects) {
+    setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+    createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+    setLayerStack(3, 1);
+
+    std::vector<uint32_t> expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 06f45f9..f4d052d 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1165,7 +1165,7 @@
             case Config::FrameRateOverride::AppOverride:
                 return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
             case Config::FrameRateOverride::Enabled:
-                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+                return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
         }
     }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
@@ -1197,7 +1197,7 @@
             case Config::FrameRateOverride::AppOverride:
                 return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
             case Config::FrameRateOverride::Enabled:
-                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+                return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
         }
     }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
@@ -2973,5 +2973,34 @@
     EXPECT_EQ(kMode1, selector.getMinRefreshRateByPolicy());
 }
 
+// TODO(b/266481656): Once this bug is fixed, we can remove this test
+TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].vote = LayerVoteType::Min;
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, frameRateIsCappedByPolicy) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    constexpr FpsRanges kCappedAt30 = {{60_Hz, 90_Hz}, {30_Hz, 30_Hz}};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {DisplayModeId(kModeId60), kCappedAt30, kCappedAt30}));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].vote = LayerVoteType::Min;
+    EXPECT_FRAME_RATE_MODE(kMode60, 30_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 3ee53c9..4b15385 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -301,9 +301,6 @@
     choice = modeChoices.get(kDisplayId1);
     ASSERT_TRUE(choice);
     EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
-
-    mScheduler->unregisterDisplay(kDisplayId1);
-    EXPECT_FALSE(mScheduler->hasRefreshRateSelectors());
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index ab732ed..88ddb0f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -262,8 +262,8 @@
         return display;
     }
 
-    static void setInitialPrimaryHWVsyncEnabled(DisplayTransactionTest* test, bool enabled) {
-        test->mFlinger.scheduler()->mutablePrimaryHWVsyncEnabled() = enabled;
+    static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, bool enabled) {
+        test->mFlinger.scheduler()->setInitialHwVsyncEnabled(enabled);
     }
 
     static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) {
@@ -329,9 +329,9 @@
     Case::Doze::setupComposerCallExpectations(this);
     auto display =
             Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE);
-    Case::setInitialPrimaryHWVsyncEnabled(this,
-                                          PowerModeInitialVSyncEnabled<
-                                                  Case::Transition::INITIAL_POWER_MODE>::value);
+    Case::setInitialHwVsyncEnabled(this,
+                                   PowerModeInitialVSyncEnabled<
+                                           Case::Transition::INITIAL_POWER_MODE>::value);
 
     // --------------------------------------------------------------------
     // Call Expectations
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 0cbfa63..bd3f3ca 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -16,11 +16,12 @@
 
 #pragma once
 
-#include <Scheduler/Scheduler.h>
 #include <ftl/fake_guard.h>
 #include <gmock/gmock.h>
 #include <gui/ISurfaceComposer.h>
 
+#include <scheduler/interface/ICompositor.h>
+
 #include "Scheduler/EventThread.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/Scheduler.h"
@@ -43,9 +44,9 @@
                       std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
-        mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker),
-                                             std::make_unique<mock::VSyncDispatch>(),
-                                             std::move(controller)));
+        mVsyncSchedule = std::unique_ptr<VsyncSchedule>(
+                new VsyncSchedule(std::move(tracker), std::make_unique<mock::VSyncDispatch>(),
+                                  std::move(controller)));
 
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr));
@@ -65,21 +66,12 @@
         return Scheduler::createConnection(std::move(eventThread));
     }
 
-    /* ------------------------------------------------------------------------
-     * Read-write access to private data to set up preconditions and assert
-     * post-conditions.
-     */
-    auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
-    auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
-
     auto refreshRateSelector() { return leaderSelectorPtr(); }
 
     const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS {
         return mRefreshRateSelectors;
     }
 
-    bool hasRefreshRateSelectors() const { return !refreshRateSelectors().empty(); }
-
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::registerDisplay(displayId, std::move(selectorPtr));
@@ -158,6 +150,12 @@
         Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
     }
 
+    void setInitialHwVsyncEnabled(bool enabled) {
+        std::lock_guard<std::mutex> lock(mVsyncSchedule->mHwVsyncLock);
+        mVsyncSchedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled
+                                                : VsyncSchedule::HwVsyncState::Disabled;
+    }
+
 private:
     // ICompositor overrides:
     void configure() override {}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 51d2012..e03f6cc 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -407,7 +407,7 @@
     }
 
     auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
-                          SurfaceFlinger::TraverseLayersFunction traverseLayers,
+                          SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers,
                           const std::shared_ptr<renderengine::ExternalTexture>& buffer,
                           bool forSystem, bool regionSampling) {
         ScreenCaptureResults captureResults;
@@ -440,13 +440,12 @@
                              uint32_t flags, const sp<IBinder>& applyToken,
                              const InputWindowCommands& inputWindowCommands,
                              int64_t desiredPresentTime, bool isAutoTimestamp,
-                             const std::vector<client_cache_t>& uncacheBuffers,
-                             bool hasListenerCallbacks,
+                             const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
                              std::vector<ListenerCallbacks>& listenerCallbacks,
                              uint64_t transactionId) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
-                                             isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
+                                             isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
                                              listenerCallbacks, transactionId);
     }
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 859f702..981e9d2 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -102,7 +102,7 @@
         int64_t desiredPresentTime = 0;
         bool isAutoTimestamp = true;
         FrameTimelineInfo frameTimelineInfo;
-        std::vector<client_cache_t> uncacheBuffers;
+        client_cache_t uncacheBuffer;
         uint64_t id = static_cast<uint64_t>(-1);
         static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
     };
@@ -138,7 +138,7 @@
                                      transaction.displays, transaction.flags,
                                      transaction.applyToken, transaction.inputWindowCommands,
                                      transaction.desiredPresentTime, transaction.isAutoTimestamp,
-                                     transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+                                     transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
                                      transaction.id);
 
         // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for
@@ -165,7 +165,7 @@
                                      transaction.displays, transaction.flags,
                                      transaction.applyToken, transaction.inputWindowCommands,
                                      transaction.desiredPresentTime, transaction.isAutoTimestamp,
-                                     transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+                                     transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
                                      transaction.id);
 
         nsecs_t returnedTime = systemTime();
@@ -196,7 +196,7 @@
                                      transactionA.displays, transactionA.flags,
                                      transactionA.applyToken, transactionA.inputWindowCommands,
                                      transactionA.desiredPresentTime, transactionA.isAutoTimestamp,
-                                     transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+                                     transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
                                      transactionA.id);
 
         // This thread should not have been blocked by the above transaction
@@ -211,7 +211,7 @@
                                      transactionB.displays, transactionB.flags,
                                      transactionB.applyToken, transactionB.inputWindowCommands,
                                      transactionB.desiredPresentTime, transactionB.isAutoTimestamp,
-                                     transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+                                     transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
                                      transactionB.id);
 
         // this thread should have been blocked by the above transaction
@@ -248,7 +248,7 @@
     mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
                                  transactionA.displays, transactionA.flags, transactionA.applyToken,
                                  transactionA.inputWindowCommands, transactionA.desiredPresentTime,
-                                 transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
+                                 transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
                                  mHasListenerCallbacks, mCallbacks, transactionA.id);
 
     auto& transactionQueue = mFlinger.getTransactionQueue();
@@ -268,7 +268,7 @@
     mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
                                  transactionA.displays, transactionA.flags, transactionA.applyToken,
                                  transactionA.inputWindowCommands, transactionA.desiredPresentTime,
-                                 transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
+                                 transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
                                  mHasListenerCallbacks, mCallbacks, transactionA.id);
 
     auto& transactionQueue = mFlinger.getTransactionQueue();
@@ -282,7 +282,7 @@
     mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags,
                                  empty.applyToken, empty.inputWindowCommands,
                                  empty.desiredPresentTime, empty.isAutoTimestamp,
-                                 empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id);
+                                 empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id);
 
     // flush transaction queue should flush as desiredPresentTime has
     // passed
@@ -379,7 +379,8 @@
                                               transaction.applyToken,
                                               transaction.inputWindowCommands,
                                               transaction.desiredPresentTime,
-                                              transaction.isAutoTimestamp, {}, systemTime(), 0,
+                                              transaction.isAutoTimestamp,
+                                              transaction.uncacheBuffer, systemTime(), 0,
                                               mHasListenerCallbacks, mCallbacks, getpid(),
                                               static_cast<int>(getuid()), transaction.id);
             mFlinger.setTransactionStateInternal(transactionState);
diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp
index b6d3a0b..b544245 100644
--- a/vulkan/vkjson/Android.bp
+++ b/vulkan/vkjson/Android.bp
@@ -25,10 +25,8 @@
         ".",
     ],
     shared_libs: [
-        "libvulkan",
-    ],
-    whole_static_libs: [
         "libjsoncpp",
+        "libvulkan",
     ],
     export_shared_lib_headers: [
         "libvulkan",