Merge "Revert "Use "SessionHint" enum in ndk API""
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..ecafcfc 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -2825,6 +2825,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 +2908,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 +2983,7 @@
                 break;
         }
     }
-    return status;
 }
-
 void Dumpstate::Cancel() {
     CleanupTmpFiles();
     android::os::UnlinkAndLogOnError(log_path_);
@@ -3181,7 +3234,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 +3323,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 +3388,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..87f9254 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() {
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 695faf8..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,28 +723,28 @@
 
 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) {
+        if (!service.hasClients && !hasKernelReportedClients) {
             sendClientCallbackNotifications(serviceName, true,
                                             "service is guaranteed to be in use");
         }
@@ -726,21 +753,23 @@
         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, "we now have a record of a client");
-        }
+    // 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) {
+    // 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,
@@ -753,13 +782,10 @@
     }
     Service& service = serviceIt->second;
 
-    CHECK(hasClients != service.hasClients)
-            << "Record shows: " << service.hasClients
-            << " so we can't tell clients again that we have client: " << hasClients
-            << " when: " << context;
+    CHECK_NE(hasClients, service.hasClients) << context;
 
-    ALOGI("Notifying %s they %s have clients when %s", serviceName.c_str(),
-          hasClients ? "do" : "don't", context);
+    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())
@@ -803,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 f9d4f8f..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,7 +93,9 @@
     void removeRegistrationCallback(const wp<IBinder>& who,
                         ServiceCallbackMap::iterator* it,
                         bool* found);
-    ssize_t handleServiceClientCallback(const std::string& serviceName, bool isCalledOnInterval);
+    // 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);
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/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..3fae4e6 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -16,9 +16,15 @@
 
 #pragma once
 
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#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 +34,51 @@
 
 /**
  * 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::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..704349c
--- /dev/null
+++ b/include/input/TfLiteMotionPredictor.h
@@ -0,0 +1,148 @@
+/*
+ * 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 <string>
+
+#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);
+
+    // 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::string 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::string 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/libs/binder/Android.bp b/libs/binder/Android.bp
index 7e7bba3..661f70c 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -287,6 +287,14 @@
     cflags: [
         "-DBINDER_WITH_KERNEL_IPC",
     ],
+    arch: {
+        // TODO(b/254713216): undefined symbol in BufferedTextOutput::getBuffer
+        riscv64: {
+            lto: {
+                thin: false,
+            },
+        },
+    },
 }
 
 cc_library {
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index b27f102..1ea13f9 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -1036,8 +1036,8 @@
                 return DEAD_OBJECT;
             }
 
-            if (it->second.asyncTodo.size() == 0) return OK;
-            if (it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) {
+            if (it->second.asyncTodo.size() != 0 &&
+                it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) {
                 LOG_RPC_DETAIL("Found next async transaction %" PRIu64 " on %" PRIu64,
                                it->second.asyncNumber, addr);
 
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/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/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/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..57e57f5 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -35,6 +35,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 +64,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;
@@ -207,7 +212,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 +282,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 +330,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 +411,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 +427,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()) {
@@ -607,7 +609,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 +664,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));
@@ -745,25 +747,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 +830,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 +852,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 +865,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 +875,7 @@
     void destroy() override {
         Surface::destroy();
 
-        std::unique_lock _lock{mMutex};
+        std::lock_guard _lock{mMutex};
         mDestroyed = true;
         mBbq = nullptr;
     }
@@ -884,7 +885,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 +895,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 +1099,7 @@
 }
 
 uint32_t BLASTBufferQueue::getLastTransformHint() const {
+    std::lock_guard _lock{mMutex};
     if (mSurfaceControl != nullptr) {
         return mSurfaceControl->getTransformHint();
     } else {
@@ -1106,18 +1108,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/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index cf9828b..ccd225c 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>
 
@@ -1182,12 +1183,14 @@
 }
 // ---------------------------------------------------------------------------
 
-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;
 }
 
@@ -2986,6 +2989,7 @@
     while (true) {
         {
             std::unique_lock<std::mutex> lock(mMutex);
+            base::ScopedLockAssertion assumeLocked(mMutex);
             callbackInfos = std::move(mCallbackInfos);
             mCallbackInfos = {};
         }
@@ -2998,6 +3002,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..8d07162 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;
@@ -252,7 +252,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 +276,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/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index c5f59c8..e3470c5 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -355,7 +355,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);
@@ -920,7 +921,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..babc197 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -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..83392ec 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: [
@@ -67,6 +73,7 @@
         "liblog",
         "libPlatformProperties",
         "libvintf",
+        "libtflite",
     ],
 
     static_libs: [
@@ -103,6 +110,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..0f889e8 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,118 +18,188 @@
 
 #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),
-        mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
+        mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)),
+        mModel(TfLiteMotionPredictorModel::create(modelPath == nullptr ? DEFAULT_MODEL_PATH
+                                                                       : modelPath)) {}
 
 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;
     }
+
+    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);
+        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..40653d3
--- /dev/null
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -0,0 +1,332 @@
+/*
+ * 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 <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <fstream>
+#include <ios>
+#include <iterator>
+#include <memory>
+#include <span>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#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/interpreter.h"
+#include "tensorflow/lite/kernels/register.h"
+#include "tensorflow/lite/model.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);
+}
+
+} // 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) {
+    std::ifstream f(modelPath, std::ios::binary);
+    LOG_ALWAYS_FATAL_IF(!f, "Could not read model from %s", modelPath);
+
+    std::string data;
+    data.assign(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>());
+
+    return std::unique_ptr<TfLiteMotionPredictorModel>(
+            new TfLiteMotionPredictorModel(std::move(data)));
+}
+
+TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(std::string model)
+      : mFlatBuffer(std::move(model)) {
+    mErrorReporter = std::make_unique<LoggingErrorReporter>();
+    mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer.data(),
+                                                               mFlatBuffer.length(),
+                                                               /*extra_verifier=*/nullptr,
+                                                               mErrorReporter.get());
+    LOG_ALWAYS_FATAL_IF(!mModel);
+
+    tflite::ops::builtin::BuiltinOpResolver resolver;
+    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();
+}
+
+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..f07164c 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,12 +19,19 @@
         "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",
         "libui-types",
@@ -32,6 +40,7 @@
         "-Wall",
         "-Wextra",
         "-Werror",
+        "-Wno-unused-parameter",
     ],
     shared_libs: [
         "libbase",
@@ -39,10 +48,14 @@
         "libcutils",
         "liblog",
         "libPlatformProperties",
+        "libtflite",
         "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..2c4b3bf 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -41,6 +41,8 @@
         "libjpegdecoder",
         "liblog",
     ],
+
+    static_libs: ["libskia"],
 }
 
 cc_library {
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 696be1b..aee6602 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -21,6 +21,7 @@
 
 namespace android::recoverymap {
 
+// 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,11 @@
     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;
 };
 
 typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
@@ -269,14 +237,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 +252,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
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
index c36a363..de29a33 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -28,13 +28,6 @@
 
 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.
  *
@@ -62,7 +55,7 @@
  *
  * below is an example of the XMP metadata that this function generates where
  * secondary_image_length = 1000
- * range_scaling_factor = 1.25
+ * max_content_boost = 8.0
  *
  * <x:xmpmeta
  *   xmlns:x="adobe:ns:meta/"
@@ -70,31 +63,26 @@
  *   <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: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/">
- *       <GContainer:Version>1</GContainer:Version>
- *       <GContainer:Directory>
+ *       <Container: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>
+ *             <Container:Item
+ *              Item:Semantic="Primary"
+ *              Item:Mime="image/jpeg"
+ *              RecoveryMap:Version="1"
+ *              RecoveryMap:MaxContentBoost="8.0"/>
  *           </rdf:li>
  *           <rdf:li>
- *             <GContainer:Item
- *               GContainer:ItemSemantic="RecoveryMap"
- *               GContainer:ItemMime="image/jpeg"
- *               GContainer:ItemLength="1000"/>
+ *             <Container:Item
+ *               Item:Semantic="RecoveryMap"
+ *               Item:Mime="image/jpeg"
+ *               Item:Length="1000"/>
  *           </rdf:li>
  *         </rdf:Seq>
- *       </GContainer:Directory>
+ *       </Container:Directory>
  *     </rdf:Description>
  *   </rdf:RDF>
  * </x:xmpmeta>
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 30aa846..8b8c2e7 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -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>
@@ -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,30 +86,19 @@
   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<recoverymap::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<
+        recoverymap::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,
@@ -141,10 +123,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 +132,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,11 +142,15 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
+  sk_sp<SkData> icc = SkWriteICCProfile(
+          jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
+          jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut));
+
   JpegEncoder jpeg_encoder;
-  // TODO: determine ICC data based on color gamut information
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
                                   uncompressed_yuv_420_image.width,
-                                  uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
+                                  uncompressed_yuv_420_image.height, quality,
+                                  icc.get()->data(), icc.get()->size())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
   jpegr_compressed_struct jpeg;
@@ -211,14 +193,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));
 
@@ -228,11 +206,15 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
+  sk_sp<SkData> icc = SkWriteICCProfile(
+          jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
+          jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut));
+
   JpegEncoder jpeg_encoder;
-  // TODO: determine ICC data based on color gamut information
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
                                   uncompressed_yuv_420_image->width,
-                                  uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
+                                  uncompressed_yuv_420_image->height, quality,
+                                  icc.get()->data(), icc.get()->size())) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
   jpegr_compressed_struct jpeg;
@@ -271,14 +253,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));
 
@@ -328,14 +306,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));
 
@@ -437,7 +411,6 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  // TODO: should we have ICC data for the map?
   JpegEncoder jpeg_encoder;
   if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
                                   uncompressed_recovery_map->width,
@@ -518,6 +491,7 @@
 
 status_t RecoveryMap::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
@@ -554,7 +528,7 @@
 
   ColorTransformFn hdrInvOetf = nullptr;
   float hdr_white_nits = 0.0f;
-  switch (metadata->transferFunction) {
+  switch (hdr_tf) {
     case JPEGR_TF_LINEAR:
       hdrInvOetf = identityConversion;
       break;
@@ -574,7 +548,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;
   }
@@ -637,9 +611,11 @@
                                        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);
@@ -656,9 +632,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->maxContentBoost);
         }
       }
     }
@@ -681,11 +657,7 @@
   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 = hdr_y_nits_max / kSdrWhiteNits;
 
   // generate map
   jobQueue.reset();
@@ -721,39 +693,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->maxContentBoost);
 
   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)) {
@@ -783,7 +737,7 @@
 #else
           Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
 #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;
@@ -811,8 +765,8 @@
 }
 
 status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                               jr_compressed_ptr primary_image,
-                                               jr_compressed_ptr recovery_map) {
+                                                        jr_compressed_ptr primary_image,
+                                                        jr_compressed_ptr recovery_map) {
   if (compressed_jpegr_image == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
index 1617b8b..40956bd 100644
--- a/libs/jpegrecoverymap/recoverymaputils.cpp
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -93,10 +93,8 @@
         string val;
         if (gContainerItemState == Started) {
             if (context.BuildTokenValue(&val)) {
-                if (!val.compare(rangeScalingFactorAttrName)) {
-                    lastAttributeName = rangeScalingFactorAttrName;
-                } else if (!val.compare(transferFunctionAttrName)) {
-                    lastAttributeName = transferFunctionAttrName;
+                if (!val.compare(maxContentBoostAttrName)) {
+                    lastAttributeName = maxContentBoostAttrName;
                 } else {
                     lastAttributeName = "";
                 }
@@ -109,22 +107,20 @@
         string val;
         if (gContainerItemState == Started) {
             if (context.BuildTokenValue(&val, true)) {
-                if (!lastAttributeName.compare(rangeScalingFactorAttrName)) {
-                    rangeScalingFactorStr = val;
-                } else if (!lastAttributeName.compare(transferFunctionAttrName)) {
-                    transferFunctionStr = val;
+                if (!lastAttributeName.compare(maxContentBoostAttrName)) {
+                    maxContentBoostStr = val;
                 }
             }
         }
         return context.GetResult();
     }
 
-    bool getRangeScalingFactor(float* scaling_factor) {
+    bool getMaxContentBoost(float* max_content_boost) {
         if (gContainerItemState == Done) {
-            stringstream ss(rangeScalingFactorStr);
+            stringstream ss(maxContentBoostStr);
             float val;
             if (ss >> val) {
-                *scaling_factor = val;
+                *max_content_boost = val;
                 return true;
             } else {
                 return false;
@@ -134,84 +130,49 @@
         }
     }
 
-    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;
+    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     = "GContainer";
+const string kContainerPrefix     = "Container";
 
 // 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;
 
+// 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 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;
+const string kMapMaxContentBoost  = Name(kRecoveryMapPrefix, "MaxContentBoost");
+const string kMapVersion          = Name(kRecoveryMapPrefix, "Version");
 
 // RecoveryMap XMP constants - names for XMP handlers
-const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor;
-const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction;
+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";
@@ -248,13 +209,10 @@
         return false;
     }
 
-    if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
+    if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) {
         return false;
     }
 
-    if (!handler.getTransferFunction(&metadata->transferFunction)) {
-        return false;
-    }
     return true;
 }
 
@@ -271,66 +229,19 @@
   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.WriteElementAndContent(kConVersion, kGContainerVersion);
   writer.StartWritingElements(kConDirSeq);
   size_t item_depth = writer.StartWritingElements(kLiItem);
-  writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary);
-  writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
+  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
+  writer.WriteAttributeNameAndValue(kItemMime, 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.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost);
   writer.FinishWritingElementsToDepth(item_depth);
   writer.StartWritingElements(kLiItem);
-  writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap);
-  writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg);
-  writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length);
+  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
+  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
+  writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
   writer.FinishWriting();
 
   return ss.str();
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 39445f8..e381caf 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -29,8 +29,8 @@
         "recoverymapmath_test.cpp",
     ],
     shared_libs: [
-        "libjpeg",
         "libimage_io",
+        "libjpeg",
         "liblog",
     ],
     static_libs: [
@@ -39,6 +39,7 @@
         "libjpegdecoder",
         "libjpegencoder",
         "libjpegrecoverymap",
+        "libskia",
     ],
 }
 
@@ -53,8 +54,8 @@
         "liblog",
     ],
     static_libs: [
-        "libjpegencoder",
         "libgtest",
+        "libjpegencoder",
     ],
 }
 
@@ -69,7 +70,7 @@
         "liblog",
     ],
     static_libs: [
-        "libjpegdecoder",
         "libgtest",
+        "libjpegdecoder",
     ],
 }
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index dfab76a..3e9a76d 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -103,8 +103,7 @@
 
 TEST_F(RecoveryMapTest, 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,8 +119,7 @@
 
   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 */
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index 1d522d1..2eec95f 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -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, float max_content_boost) {
     Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
     Color rgb = srgbInvOetf(rgb_gamma);
-    return applyRecovery(rgb, recovery, range_scaling_factor);
+    return applyRecovery(rgb, recovery, max_content_boost);
   }
 
   jpegr_uncompressed_struct Yuv420Image() {
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..304f907
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -0,0 +1,670 @@
+/*
+ ** 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 <android-base/properties.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()) {
+        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;
+
+    // Initialize our cache with the contents of the directory
+    mTotalCacheSize = 0;
+
+    // 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));
+
+                // 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) {
+                    if (remove(fullPath.c_str()) != 0) {
+                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                    }
+                    continue;
+                }
+
+                // 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);
+        }
+    }
+
+    mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
+
+    mInitialized = true;
+}
+
+MultifileBlobCache::~MultifileBlobCache() {
+    // 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();
+    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() {
+    // 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..c11dfb7
--- /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; }
+    void trimCache(size_t cacheByteLimit);
+
+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);
+
+    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..48f9f2b 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 {
@@ -47,7 +48,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..079b80d 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;
         }
     }
@@ -2298,13 +2308,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 +2327,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 +2347,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 +2382,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 +2408,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 +2457,7 @@
                 if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
                     continue;
                 }
-                touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+                touchedWindow.pointerIds.set(entry.pointerProperties[pointerIndex].id);
             }
         }
     }
@@ -2509,6 +2531,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 +2586,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 +2618,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 +2675,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 +2712,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 +3022,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 +3192,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 +3365,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 +3501,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 +3516,7 @@
                       "status=%s(%d)",
                       connection->getInputChannelName().c_str(), statusToString(status).c_str(),
                       status);
-                abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+                abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
             }
             return;
         }
@@ -3868,8 +3897,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 +3913,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 +3929,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 +3943,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 +3969,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();
@@ -4224,7 +4254,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 +5254,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 +5275,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 +5648,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 +5686,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 +5712,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 +5775,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 +5805,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 +6417,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 +6574,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 +6611,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..acfd0a2 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -33,7 +33,8 @@
 
 void TouchState::removeTouchedPointer(int32_t pointerId) {
     for (TouchedWindow& touchedWindow : windows) {
-        touchedWindow.pointerIds.clearBit(pointerId);
+        touchedWindow.pointerIds.reset(pointerId);
+        touchedWindow.pilferedPointerIds.reset(pointerId);
     }
 }
 
@@ -41,7 +42,8 @@
         int32_t pointerId, const sp<android::gui::WindowInfoHandle>& windowHandle) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.pointerIds.clearBit(pointerId);
+            touchedWindow.pointerIds.reset(pointerId);
+            touchedWindow.pilferedPointerIds.reset(pointerId);
             return;
         }
     }
@@ -55,13 +57,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 +76,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 +87,7 @@
     touchedWindow.windowHandle = windowHandle;
     touchedWindow.targetFlags = targetFlags;
     touchedWindow.pointerIds = pointerIds;
-    touchedWindow.firstDownTimeInTarget = eventTime;
+    touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;
     windows.push_back(touchedWindow);
 }
 
@@ -126,25 +129,54 @@
     }
 }
 
-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;
+    });
+
+    // 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;
+        // TODO(b/211379801) : convert pointerIds to use std::bitset, which would allow us to
+        // replace the loop below with a bitwise operation. Currently, the XOR operation above is
+        // redundant, but is done to make the code more explicit / easier to convert later.
+        for (std::size_t i = 0; i < pilferedByOtherWindows.size(); i++) {
+            if (pilferedByOtherWindows.test(i) && !w.pilferedPointerIds.test(i)) {
+                // Pointer is pilfered by other windows, but not by this one! Remove it from here.
+                // We could call 'removeTouchedPointerFromWindow' here, but it's faster to directly
+                // manipulate it.
+                w.pointerIds.reset(i);
+            }
         }
     });
-    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
+    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); });
 }
 
 sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
@@ -193,7 +225,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 +244,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..92f62b5 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();
 }
@@ -62,11 +66,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..e59e781 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -30,13 +30,15 @@
 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);
@@ -45,7 +47,7 @@
     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/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..b1b6e05 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};
@@ -1545,7 +1590,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 +1615,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 +1644,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 +1665,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 +1895,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 +1971,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 +1982,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 +1996,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 +2055,123 @@
 }
 
 /**
+ * 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();
+}
+
+/**
  * 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 +2423,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 +2785,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 +2808,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 +2990,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 +3108,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 +3392,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 +3765,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 +3806,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 +3983,7 @@
 
     mDispatcher->notifyMotion(&motionArgs);
     window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
-                         0 /*expectedFlags*/);
+                         /*expectedFlags=*/0);
 }
 
 /**
@@ -3740,35 +4004,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 +4048,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 +4309,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 +4467,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 +4491,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 +4534,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 +4544,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 +4890,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 +4929,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 +4949,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 +5067,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 +5344,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 +5373,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 +5408,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 +5432,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 +5459,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 +5483,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 +5541,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 +5664,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 +5699,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 +5734,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 +5817,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 +5891,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 +5980,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 +6020,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 +6082,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 +6134,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 +6324,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 +6973,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 +7029,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 +7173,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 +7200,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 +7216,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 +7230,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 +7261,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 +7354,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 +7400,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 +7446,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 +7505,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 +7513,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 +7535,7 @@
     const WindowInfo& windowInfo = *mWindow->getInfo();
     changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode,
                                               windowInfo.ownerPid, windowInfo.ownerUid,
-                                              false /*  hasPermission */);
+                                              /* hasPermission=*/false);
 }
 
 TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTouchMode) {
@@ -7284,7 +7544,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 +7556,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 +7572,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 +7592,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 +7804,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 +7836,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 +7875,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 +7884,7 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionPointerDown(1 /* pointerIndex */);
+    spy->consumeMotionPointerDown(/*pointerIndex=*/1);
 }
 
 /**
@@ -7742,33 +7998,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 +8049,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 +8064,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 +8110,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 +8157,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 +8197,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 +8321,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/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..1348548 100644
--- a/services/stats/StatsAidl.cpp
+++ b/services/stats/StatsAidl.cpp
@@ -14,32 +14,33 @@
  * limitations under the License.
  */
 
-#define DEBUG false // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #define LOG_TAG "StatsAidl"
 
+#include "StatsAidl.h"
+
 #include <log/log.h>
 #include <statslog.h>
 
-#include "StatsAidl.h"
-
 namespace aidl {
 namespace android {
 namespace frameworks {
 namespace stats {
 
-StatsHal::StatsHal() {}
+StatsHal::StatsHal() {
+}
 
 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);
@@ -47,24 +48,20 @@
     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 +109,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());
@@ -152,9 +149,9 @@
     const int ret = AStatsEvent_write(event);
     AStatsEvent_release(event);
 
-    return ret <= 0 ?
-            ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret, "report atom failed") :
-            ndk::ScopedAStatus::ok();
+    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/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/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/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..6b5d1d7 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -115,7 +115,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 +244,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 +284,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;
@@ -316,6 +330,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 +361,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/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 cc26591..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 =
@@ -714,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) {
@@ -867,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,
@@ -990,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 b7fa4f0..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;
     }
 
@@ -309,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/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 7a4b337..31ee91e 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;
@@ -254,7 +254,7 @@
     }
     if (hasTrustedPresentationListener()) {
         mFlinger->mNumTrustedPresentationListeners--;
-        updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/);
+        updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/);
     }
 }
 
@@ -285,7 +285,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 +384,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 +395,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);
         }
     }
@@ -3096,15 +3098,14 @@
     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();
-
     for (const auto& handle : handles) {
         // If this transaction set a buffer on this layer, release its previous buffer
         handle->releasePreviousBuffer = mReleasePreviousBuffer;
@@ -3178,11 +3179,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) {
@@ -3319,39 +3319,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);
@@ -3601,7 +3568,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 {
@@ -3612,16 +3579,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;
@@ -3821,54 +3808,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;
@@ -4014,7 +3953,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();
@@ -4066,28 +4004,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 3384e4a..3d4f03f 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -307,7 +307,8 @@
     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);
@@ -328,9 +329,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
@@ -512,7 +516,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) {
@@ -539,7 +543,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() {
@@ -831,6 +836,7 @@
     void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
     void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
                                         std::unordered_set<Layer*>& visited);
+    bool willPresentCurrentTransaction() const;
 
 protected:
     // For unit tests
@@ -1036,12 +1042,6 @@
     // 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,
@@ -1149,34 +1149,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/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 3c20e3b..910fce0 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -25,14 +25,12 @@
     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) {
@@ -58,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
@@ -98,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/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 1d27cfc..c5b3e14 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -929,17 +929,38 @@
                                          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) {
-        using fps_approx_ops::operator<;
         const auto& modePtr = frameRateMode.modePtr;
         if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) {
             return;
         }
 
         const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending);
-        if (ascending && frameRateMode.fps < getMinRefreshRateByPolicyLocked()->getFps()) {
+        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;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 1fc1519..c314b5c 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -34,6 +34,8 @@
 #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();
 }
 
@@ -166,6 +174,10 @@
     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 +229,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) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ef7d0cf..796c785 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -158,7 +158,8 @@
                                        std::chrono::nanoseconds readyDuration);
 
     sp<IDisplayEventConnection> createDisplayEventConnection(
-            ConnectionHandle, EventRegistrationFlags eventRegistration = {});
+            ConnectionHandle, EventRegistrationFlags eventRegistration = {},
+            const sp<IBinder>& layerHandle = nullptr);
 
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
@@ -231,6 +232,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 +266,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 +288,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);
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 7d0dc93..169e101 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -173,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;
@@ -408,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);
@@ -477,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() {
@@ -512,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.
@@ -543,6 +543,7 @@
     DisplayDeviceState state;
     state.isSecure = secure;
     state.displayName = displayName;
+    state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
     mCurrentState.displays.add(token, state);
     return token;
 }
@@ -1976,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) {
@@ -2135,6 +2137,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
@@ -2155,11 +2261,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",
@@ -2182,9 +2288,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++;
@@ -2222,7 +2333,7 @@
     }
 
     if (framePending) {
-        if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) {
+        if (mBackpressureGpuComposition || (hwcFrameMissed && !gpuFrameMissed)) {
             scheduleCommit(FrameHint::kNone);
             return false;
         }
@@ -2269,45 +2380,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
@@ -2343,7 +2443,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);
@@ -2386,15 +2494,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)) {
@@ -2421,17 +2520,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);
@@ -2459,29 +2554,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)) {
@@ -2511,7 +2620,7 @@
     for (auto& layer : mLayersPendingRefresh) {
         Region visibleReg;
         visibleReg.set(layer->getScreenBounds());
-        invalidateLayerStack(layer, visibleReg);
+        invalidateLayerStack(layer->getOutputFilter(), visibleReg);
     }
     mLayersPendingRefresh.clear();
 }
@@ -2586,12 +2695,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 {
@@ -2600,8 +2710,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};
@@ -2632,7 +2743,7 @@
                                             presentLatency.ns());
 
     for (const auto& layer: mLayersWithQueuedFrames) {
-        layer->onPostComposition(display, glCompositionDoneFenceTime, presentFenceTime,
+        layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime,
                                  compositorTiming);
         layer->releasePendingBuffer(presentTime.ns());
     }
@@ -2700,22 +2811,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();
         }
     }
@@ -2724,7 +2835,7 @@
     const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle);
     mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections);
 
-    if (isDisplayConnected && !display->isPoweredOn()) {
+    if (isDisplayConnected && !defaultDisplay->isPoweredOn()) {
         getRenderEngine().cleanupPostRender();
         return;
     }
@@ -2750,13 +2861,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);
         });
     }
 
@@ -3082,6 +3204,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();
@@ -3198,6 +3322,10 @@
         dispatchDisplayHotplugEvent(displayId, true);
     }
 
+    if (display->isVirtual()) {
+        display->adjustRefreshRate(mScheduler->getLeaderRefreshRate());
+    }
+
     mDisplays.try_emplace(displayToken, std::move(display));
 }
 
@@ -3349,7 +3477,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) {
@@ -3440,7 +3569,7 @@
                 // this layer is not visible anymore
                 Region visibleReg;
                 visibleReg.set(layer->getScreenBounds());
-                invalidateLayerStack(sp<Layer>::fromExisting(layer), visibleReg);
+                invalidateLayerStack(layer->getOutputFilter(), visibleReg);
             }
         });
     }
@@ -3528,16 +3657,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();
 
@@ -3554,17 +3690,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) {
@@ -3740,10 +3868,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);
         }
     }
@@ -3863,6 +3991,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);
@@ -3874,14 +4003,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
@@ -4196,9 +4329,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;
 }
 
@@ -4213,9 +4345,11 @@
                                            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
@@ -4227,9 +4361,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;
@@ -4262,8 +4403,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;
@@ -4276,6 +4417,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;
@@ -4657,7 +4834,11 @@
                                           s.trustedPresentationListener);
     }
 
-    if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded;
+    if (layer->setTransactionCompletedListeners(callbackHandles,
+                                                layer->willPresentCurrentTransaction())) {
+        flags |= eTraversalNeeded;
+    }
+
     // Do not put anything that updates layer state or modifies flags after
     // setTransactionCompletedListener
 
@@ -4670,6 +4851,94 @@
     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.registerUnpresentedCallbackHandle(
+                    sp<CallbackHandle>::make(listener, callbackIds, s.surface));
+        }
+        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;
@@ -4738,6 +5007,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());
@@ -4840,7 +5110,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);
@@ -4848,6 +5123,8 @@
     if (mTransactionTracing) {
         mTransactionTracing->onHandleRemoved(handle);
     }
+
+    setTransactionFlags(eTransactionFlushNeeded);
 }
 
 void SurfaceFlinger::onInitializeDisplays() {
@@ -6417,10 +6694,15 @@
                                          args.useIdentityTransform, args.captureSecureLayers);
     });
 
-    auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) {
-        traverseLayersInLayerStack(layerStack, args.uid, visitor);
-    };
-    auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    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), getLayerSnapshots, reqSize,
                                       args.pixelFormat, args.allowProtected, args.grayscale,
@@ -6454,10 +6736,15 @@
                                          false /* captureSecureLayers */);
     });
 
-    auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
-        traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor);
-    };
-    auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    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");
@@ -6547,37 +6834,42 @@
         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, 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;
-            }
-
-            auto p = sp<Layer>::fromExisting(layer);
-            while (p != nullptr) {
-                if (excludeLayerIds.count(p->sequence) != 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 getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+                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");
@@ -6708,9 +7000,6 @@
         ScreenCaptureResults& captureResults) {
     ATRACE_CALL();
 
-    const auto& display = renderArea->getDisplayDevice();
-    const auto& transform = renderArea->getTransform();
-    std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
     auto layers = getLayerSnapshots();
     for (auto& [layer, layerFE] : layers) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
@@ -6718,9 +7007,6 @@
         captureResults.capturedHdrLayers |= isHdrLayer(*snapshot);
         layerFE->mSnapshot->geomLayerTransform =
                 renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
-        if (layer->needsFilteringForScreenshots(display.get(), transform)) {
-            filterForScreenshot.insert(layerFE.get());
-        }
     }
 
     // We allow the system server to take screenshots of secure layers for
@@ -6771,9 +7057,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());
@@ -6789,7 +7075,6 @@
                                         .buffer = std::move(buffer),
                                         .sdrWhitePointNits = sdrWhitePointNits,
                                         .displayBrightnessNits = displayBrightnessNits,
-                                        .filterForScreenshot = std::move(filterForScreenshot),
                                         .regionSampling = regionSampling});
 
         const float colorSaturation = grayscale ? 0 : 1;
@@ -7366,24 +7651,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() {
@@ -7411,6 +7690,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 (auto layerFE = layer->getCompositionEngineLayerFE()) {
+                if (cursorOnly &&
+                    layer->getLayerSnapshot()->compositionType !=
+                            aidl::android::hardware::graphics::composer3::Composition::CURSOR)
+                    return;
+                layer->updateSnapshot(/* refreshArgs.updatingGeometryThisFrame */ true);
+                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() {
@@ -7424,9 +7847,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);
@@ -7448,13 +7871,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 207dfe2..5b8038b 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,7 +71,9 @@
 #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/RefreshRateSelector.h"
@@ -447,6 +451,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;
@@ -483,7 +507,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);
@@ -505,7 +530,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>&);
@@ -606,19 +632,9 @@
     void onComposerHalVsyncIdle(hal::HWDisplayId) 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:
@@ -694,6 +710,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);
@@ -721,6 +748,8 @@
     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();
@@ -736,7 +765,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.
@@ -894,7 +926,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) {
@@ -1152,23 +1184,13 @@
     // 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 +1226,7 @@
     std::atomic_bool mForceFullDamage = false;
 
     bool mLayerCachingEnabled = false;
-    bool mPropagateBackpressureClientComposition = false;
+    bool mBackpressureGpuComposition = false;
 
     LayerTracing mLayerTracing{*this};
     bool mLayerTracingEnabled = false;
@@ -1268,6 +1290,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};
@@ -1334,23 +1359,11 @@
             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;
@@ -1372,6 +1385,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;
 
@@ -1384,6 +1402,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;
@@ -1391,6 +1421,7 @@
 
     TransactionHandler mTransactionHandler;
     display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
+    bool mFrontEndDisplayInfosChanged = false;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
@@ -1400,10 +1431,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;
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index e5de759..3da98d4 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -137,7 +137,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 +146,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 +156,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/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/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/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/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/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 5fddda5..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());
@@ -2983,5 +2983,24 @@
     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/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 0cbfa63..6cf6141 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"
@@ -78,8 +79,6 @@
         return mRefreshRateSelectors;
     }
 
-    bool hasRefreshRateSelectors() const { return !refreshRateSelectors().empty(); }
-
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::registerDisplay(displayId, std::move(selectorPtr));