Merge "Revert "SF: Flow DisplayModeRequest through mode set FSM"" into main
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 83b336c..b99f443 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -178,6 +178,7 @@
 #define PROFILE_DATA_DIR_CUR "/data/misc/profiles/cur"
 #define PROFILE_DATA_DIR_REF "/data/misc/profiles/ref"
 #define XFRM_STAT_PROC_FILE "/proc/net/xfrm_stat"
+#define KERNEL_CONFIG "/proc/config.gz"
 #define WLUTIL "/vendor/xbin/wlutil"
 #define WMTRACE_DATA_DIR "/data/misc/wmtrace"
 #define OTA_METADATA_DIR "/metadata/ota"
@@ -1951,6 +1952,8 @@
     DumpFile("PSI memory", "/proc/pressure/memory");
     DumpFile("PSI io", "/proc/pressure/io");
 
+    ds.AddZipEntry(ZIP_ROOT_DIR + KERNEL_CONFIG, KERNEL_CONFIG);
+
     RunCommand("SDK EXTENSIONS", {SDK_EXT_INFO, "--dump"},
                CommandOptions::WithTimeout(10).Always().DropRoot().Build());
 
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index e54f9d3..870e8eb 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -44,6 +44,7 @@
 #include "Timeout.h"
 #include "utils.h"
 
+using ::android::hardware::hidl_array;
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
 using ::android::hidl::base::V1_0::DebugInfo;
@@ -522,27 +523,35 @@
     using namespace ::android::hardware;
     using namespace ::android::hidl::manager::V1_0;
     using namespace ::android::hidl::base::V1_0;
-    using std::literals::chrono_literals::operator""s;
-    auto ret = timeoutIPC(10s, manager, &IServiceManager::debugDump, [&] (const auto &infos) {
-        std::map<std::string, TableEntry> entries;
-        for (const auto &info : infos) {
-            std::string interfaceName = std::string{info.interfaceName.c_str()} + "/" +
-                    std::string{info.instanceName.c_str()};
-            entries.emplace(interfaceName, TableEntry{
-                .interfaceName = interfaceName,
-                .transport = vintf::Transport::PASSTHROUGH,
-                .clientPids = info.clientPids,
-            }).first->second.arch |= fromBaseArchitecture(info.arch);
-        }
-        for (auto &&pair : entries) {
-            putEntry(HalType::PASSTHROUGH_LIBRARIES, std::move(pair.second));
-        }
-    });
+
+    // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+    // even though the interface function call is synchronous.
+    // However, there's no need to lock because if ret.isOk(), the background thread has
+    // already ended, so it is safe to dereference entries.
+    auto entries = std::make_shared<std::map<std::string, TableEntry>>();
+    auto ret =
+            timeoutIPC(mLshal.getDebugDumpWait(), manager, &IServiceManager::debugDump,
+                       [entries](const auto& infos) {
+                           for (const auto& info : infos) {
+                               std::string interfaceName = std::string{info.interfaceName.c_str()} +
+                                       "/" + std::string{info.instanceName.c_str()};
+                               entries->emplace(interfaceName,
+                                                TableEntry{
+                                                        .interfaceName = interfaceName,
+                                                        .transport = vintf::Transport::PASSTHROUGH,
+                                                        .clientPids = info.clientPids,
+                                                })
+                                       .first->second.arch |= fromBaseArchitecture(info.arch);
+                           }
+                       });
     if (!ret.isOk()) {
         err() << "Error: Failed to call list on getPassthroughServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_ALL_LIBS_ERROR;
     }
+    for (auto&& pair : *entries) {
+        putEntry(HalType::PASSTHROUGH_LIBRARIES, std::move(pair.second));
+    }
     return OK;
 }
 
@@ -553,27 +562,40 @@
     using namespace ::android::hardware::details;
     using namespace ::android::hidl::manager::V1_0;
     using namespace ::android::hidl::base::V1_0;
-    auto ret = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) {
-        for (const auto &info : infos) {
-            if (info.clientPids.size() <= 0) {
-                continue;
-            }
-            putEntry(HalType::PASSTHROUGH_CLIENTS, {
-                .interfaceName =
-                        std::string{info.interfaceName.c_str()} + "/" +
-                        std::string{info.instanceName.c_str()},
-                .transport = vintf::Transport::PASSTHROUGH,
-                .serverPid = info.clientPids.size() == 1 ? info.clientPids[0] : NO_PID,
-                .clientPids = info.clientPids,
-                .arch = fromBaseArchitecture(info.arch)
-            });
-        }
-    });
+
+    // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+    // even though the interface function call is synchronous.
+    // However, there's no need to lock because if ret.isOk(), the background thread has
+    // already ended, so it is safe to dereference entries.
+    auto entries = std::make_shared<std::vector<TableEntry>>();
+    auto ret =
+            timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::debugDump,
+                       [entries](const auto& infos) {
+                           for (const auto& info : infos) {
+                               if (info.clientPids.size() <= 0) {
+                                   continue;
+                               }
+                               entries->emplace_back(
+                                       TableEntry{.interfaceName =
+                                                          std::string{info.interfaceName.c_str()} +
+                                                          "/" +
+                                                          std::string{info.instanceName.c_str()},
+                                                  .transport = vintf::Transport::PASSTHROUGH,
+                                                  .serverPid = info.clientPids.size() == 1
+                                                          ? info.clientPids[0]
+                                                          : NO_PID,
+                                                  .clientPids = info.clientPids,
+                                                  .arch = fromBaseArchitecture(info.arch)});
+                           }
+                       });
     if (!ret.isOk()) {
         err() << "Error: Failed to call debugDump on defaultServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_PASSTHROUGH_ERROR;
     }
+    for (auto&& entry : *entries) {
+        putEntry(HalType::PASSTHROUGH_CLIENTS, std::move(entry));
+    }
     return OK;
 }
 
@@ -583,11 +605,14 @@
     if (!shouldFetchHalType(HalType::BINDERIZED_SERVICES)) { return OK; }
 
     const vintf::Transport mode = vintf::Transport::HWBINDER;
-    hidl_vec<hidl_string> fqInstanceNames;
-    // copying out for timeoutIPC
-    auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) {
-        fqInstanceNames = names;
-    });
+
+    // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+    // even though the interface function call is synchronous.
+    // However, there's no need to lock because if listRet.isOk(), the background thread has
+    // already ended, so it is safe to dereference fqInstanceNames.
+    auto fqInstanceNames = std::make_shared<hidl_vec<hidl_string>>();
+    auto listRet = timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::list,
+                              [fqInstanceNames](const auto& names) { *fqInstanceNames = names; });
     if (!listRet.isOk()) {
         err() << "Error: Failed to list services for " << mode << ": "
              << listRet.description() << std::endl;
@@ -596,7 +621,7 @@
 
     Status status = OK;
     std::map<std::string, TableEntry> allTableEntries;
-    for (const auto &fqInstanceName : fqInstanceNames) {
+    for (const auto& fqInstanceName : *fqInstanceNames) {
         // create entry and default assign all fields.
         TableEntry& entry = allTableEntries[fqInstanceName];
         entry.interfaceName = fqInstanceName;
@@ -623,7 +648,8 @@
     const auto pair = splitFirst(entry->interfaceName, '/');
     const auto &serviceName = pair.first;
     const auto &instanceName = pair.second;
-    auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName);
+    auto getRet = timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::get, serviceName,
+                             instanceName);
     if (!getRet.isOk()) {
         handleError(TRANSACTION_ERROR,
                     "cannot be fetched from service manager:" + getRet.description());
@@ -637,30 +663,33 @@
 
     // getDebugInfo
     do {
-        DebugInfo debugInfo;
-        auto debugRet = timeoutIPC(service, &IBase::getDebugInfo, [&] (const auto &received) {
-            debugInfo = received;
-        });
+        // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+        // even though the interface function call is synchronous.
+        // However, there's no need to lock because if debugRet.isOk(), the background thread has
+        // already ended, so it is safe to dereference debugInfo.
+        auto debugInfo = std::make_shared<DebugInfo>();
+        auto debugRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::getDebugInfo,
+                                   [debugInfo](const auto& received) { *debugInfo = received; });
         if (!debugRet.isOk()) {
             handleError(TRANSACTION_ERROR,
                         "debugging information cannot be retrieved: " + debugRet.description());
             break; // skip getPidInfo
         }
 
-        entry->serverPid = debugInfo.pid;
-        entry->serverObjectAddress = debugInfo.ptr;
-        entry->arch = fromBaseArchitecture(debugInfo.arch);
+        entry->serverPid = debugInfo->pid;
+        entry->serverObjectAddress = debugInfo->ptr;
+        entry->arch = fromBaseArchitecture(debugInfo->arch);
 
-        if (debugInfo.pid != NO_PID) {
-            const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
+        if (debugInfo->pid != NO_PID) {
+            const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo->pid);
             if (pidInfo == nullptr) {
                 handleError(IO_ERROR,
-                            "no information for PID " + std::to_string(debugInfo.pid) +
-                            ", are you root?");
+                            "no information for PID " + std::to_string(debugInfo->pid) +
+                                    ", are you root?");
                 break;
             }
-            if (debugInfo.ptr != NO_PTR) {
-                auto it = pidInfo->refPids.find(debugInfo.ptr);
+            if (debugInfo->ptr != NO_PTR) {
+                auto it = pidInfo->refPids.find(debugInfo->ptr);
                 if (it != pidInfo->refPids.end()) {
                     entry->clientPids = it->second;
                 }
@@ -672,39 +701,46 @@
 
     // hash
     do {
-        ssize_t hashIndex = -1;
-        auto ifaceChainRet = timeoutIPC(service, &IBase::interfaceChain, [&] (const auto& c) {
-            for (size_t i = 0; i < c.size(); ++i) {
-                if (serviceName == c[i]) {
-                    hashIndex = static_cast<ssize_t>(i);
-                    break;
-                }
-            }
-        });
+        // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+        // even though the interface function call is synchronous.
+        auto hashIndexStore = std::make_shared<ssize_t>(-1);
+        auto ifaceChainRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::interfaceChain,
+                                        [hashIndexStore, serviceName](const auto& c) {
+                                            for (size_t i = 0; i < c.size(); ++i) {
+                                                if (serviceName == c[i]) {
+                                                    *hashIndexStore = static_cast<ssize_t>(i);
+                                                    break;
+                                                }
+                                            }
+                                        });
         if (!ifaceChainRet.isOk()) {
             handleError(TRANSACTION_ERROR,
                         "interfaceChain fails: " + ifaceChainRet.description());
             break; // skip getHashChain
         }
+        // if ifaceChainRet.isOk(), the background thread has already ended, so it is safe to
+        // dereference hashIndex without any locking.
+        auto hashIndex = *hashIndexStore;
         if (hashIndex < 0) {
             handleError(BAD_IMPL, "Interface name does not exist in interfaceChain.");
             break; // skip getHashChain
         }
-        auto hashRet = timeoutIPC(service, &IBase::getHashChain, [&] (const auto& hashChain) {
-            if (static_cast<size_t>(hashIndex) >= hashChain.size()) {
-                handleError(BAD_IMPL,
-                            "interfaceChain indicates position " + std::to_string(hashIndex) +
-                            " but getHashChain returns " + std::to_string(hashChain.size()) +
-                            " hashes");
-                return;
-            }
-
-            auto&& hashArray = hashChain[hashIndex];
-            entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
-        });
+        // See comments about hashIndex above.
+        auto hashChain = std::make_shared<hidl_vec<hidl_array<uint8_t, 32>>>();
+        auto hashRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::getHashChain,
+                                  [hashChain](const auto& ret) { *hashChain = std::move(ret); });
         if (!hashRet.isOk()) {
             handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
         }
+        if (static_cast<size_t>(hashIndex) >= hashChain->size()) {
+            handleError(BAD_IMPL,
+                        "interfaceChain indicates position " + std::to_string(hashIndex) +
+                                " but getHashChain returns " + std::to_string(hashChain->size()) +
+                                " hashes");
+        } else {
+            auto&& hashArray = (*hashChain)[hashIndex];
+            entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
+        }
     } while (0);
     if (status == OK) {
         entry->serviceStatus = ServiceStatus::ALIVE;
diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp
index a5f98c2..6115da7 100644
--- a/cmds/lshal/Lshal.cpp
+++ b/cmds/lshal/Lshal.cpp
@@ -250,5 +250,17 @@
     return mPassthroughManager;
 }
 
+void Lshal::setWaitTimeForTest(std::chrono::milliseconds ipcCallWait,
+                               std::chrono::milliseconds debugDumpWait) {
+    mIpcCallWait = ipcCallWait;
+    mDebugDumpWait = debugDumpWait;
+}
+std::chrono::milliseconds Lshal::getIpcCallWait() const {
+    return mIpcCallWait;
+}
+std::chrono::milliseconds Lshal::getDebugDumpWait() const {
+    return mDebugDumpWait;
+}
+
 }  // namespace lshal
 }  // namespace android
diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h
index 50279d4..cb2820c 100644
--- a/cmds/lshal/Lshal.h
+++ b/cmds/lshal/Lshal.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <chrono>
 #include <iostream>
 #include <string>
 
@@ -58,6 +59,11 @@
 
     void forEachCommand(const std::function<void(const Command* c)>& f) const;
 
+    void setWaitTimeForTest(std::chrono::milliseconds ipcCallWait,
+                            std::chrono::milliseconds debugDumpWait);
+    std::chrono::milliseconds getIpcCallWait() const;
+    std::chrono::milliseconds getDebugDumpWait() const;
+
 private:
     Status parseArgs(const Arg &arg);
 
@@ -70,6 +76,9 @@
 
     std::vector<std::unique_ptr<Command>> mRegisteredCommands;
 
+    std::chrono::milliseconds mIpcCallWait{500};
+    std::chrono::milliseconds mDebugDumpWait{10000};
+
     DISALLOW_COPY_AND_ASSIGN(Lshal);
 };
 
diff --git a/cmds/lshal/Timeout.h b/cmds/lshal/Timeout.h
index e8d22d9..805e8dc 100644
--- a/cmds/lshal/Timeout.h
+++ b/cmds/lshal/Timeout.h
@@ -27,8 +27,6 @@
 namespace android {
 namespace lshal {
 
-static constexpr std::chrono::milliseconds IPC_CALL_WAIT{500};
-
 class BackgroundTaskState {
 public:
     explicit BackgroundTaskState(std::function<void(void)> &&func)
@@ -96,12 +94,5 @@
     return ret;
 }
 
-template<class Function, class I, class... Args>
-typename std::result_of<Function(I *, Args...)>::type
-timeoutIPC(const sp<I> &interfaceObject, Function &&func, Args &&... args) {
-    return timeoutIPC(IPC_CALL_WAIT, interfaceObject, func, args...);
-}
-
-
 }  // namespace lshal
 }  // namespace android
diff --git a/include/input/Input.h b/include/input/Input.h
index 1c4ea6b..7b253a5 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -515,6 +515,8 @@
     PointerProperties& operator=(const PointerProperties&) = default;
 };
 
+std::ostream& operator<<(std::ostream& out, const PointerProperties& properties);
+
 // TODO(b/211379801) : Use a strong type from ftl/mixins.h instead
 using DeviceId = int32_t;
 
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 83fffa3..3470be4 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -117,11 +117,12 @@
 template <typename T>
 std::string dumpVector(const std::vector<T>& values,
                        std::string (*valueToString)(const T&) = constToString) {
-    std::string dump = valueToString(values[0]);
-    for (size_t i = 1; i < values.size(); i++) {
-        dump += ", " + valueToString(values[i]);
+    std::string out;
+    for (const auto& value : values) {
+        out += out.empty() ? "[" : ", ";
+        out += valueToString(value);
     }
-    return dump;
+    return out.empty() ? "[]" : (out + "]");
 }
 
 const char* toString(bool value);
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index bf3699c..ae0fb01 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -258,7 +258,9 @@
         "-DBINDER_DISABLE_NATIVE_HANDLE",
         "-DBINDER_DISABLE_BLOB",
         "-DBINDER_NO_LIBBASE",
+        // TODO: switch to "vendor: true" rather than copying this
         // Trusty libbinder uses vendor stability for its binders
+        "-D__ANDROID_VENDOR__",
         "-D__ANDROID_VNDK__",
         "-U__ANDROID__",
         "-D__TRUSTY__",
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 9341eff..b92e504 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -67,28 +67,28 @@
 
 // Static const and functions will be optimized out if not used,
 // when LOG_NDEBUG and references in IF_LOG_COMMANDS() are optimized out.
-static const char *kReturnStrings[] = {
-    "BR_ERROR",
-    "BR_OK",
-    "BR_TRANSACTION",
-    "BR_REPLY",
-    "BR_ACQUIRE_RESULT",
-    "BR_DEAD_REPLY",
-    "BR_TRANSACTION_COMPLETE",
-    "BR_INCREFS",
-    "BR_ACQUIRE",
-    "BR_RELEASE",
-    "BR_DECREFS",
-    "BR_ATTEMPT_ACQUIRE",
-    "BR_NOOP",
-    "BR_SPAWN_LOOPER",
-    "BR_FINISHED",
-    "BR_DEAD_BINDER",
-    "BR_CLEAR_DEATH_NOTIFICATION_DONE",
-    "BR_FAILED_REPLY",
-    "BR_FROZEN_REPLY",
-    "BR_ONEWAY_SPAM_SUSPECT",
-    "BR_TRANSACTION_SEC_CTX",
+static const char* kReturnStrings[] = {
+        "BR_ERROR",
+        "BR_OK",
+        "BR_TRANSACTION/BR_TRANSACTION_SEC_CTX",
+        "BR_REPLY",
+        "BR_ACQUIRE_RESULT",
+        "BR_DEAD_REPLY",
+        "BR_TRANSACTION_COMPLETE",
+        "BR_INCREFS",
+        "BR_ACQUIRE",
+        "BR_RELEASE",
+        "BR_DECREFS",
+        "BR_ATTEMPT_ACQUIRE",
+        "BR_NOOP",
+        "BR_SPAWN_LOOPER",
+        "BR_FINISHED",
+        "BR_DEAD_BINDER",
+        "BR_CLEAR_DEATH_NOTIFICATION_DONE",
+        "BR_FAILED_REPLY",
+        "BR_FROZEN_REPLY",
+        "BR_ONEWAY_SPAM_SUSPECT",
+        "BR_TRANSACTION_PENDING_FROZEN",
 };
 
 static const char *kCommandStrings[] = {
diff --git a/libs/binder/Stability.cpp b/libs/binder/Stability.cpp
index c432b3a..665dfea 100644
--- a/libs/binder/Stability.cpp
+++ b/libs/binder/Stability.cpp
@@ -73,6 +73,14 @@
     (void)setRepr(binder, getLocalLevel(), REPR_NONE);
 }
 
+// after deprecation of the VNDK, these should be aliases. At some point
+// all references to __ANDROID_VNDK__ should be replaced by __ANDROID_VENDOR__
+// but for right now, check that this condition holds because some
+// places check __ANDROID_VNDK__ and some places check __ANDROID_VENDOR__
+#if defined(__ANDROID_VNDK__) != defined(__ANDROID_VENDOR__)
+#error "__ANDROID_VNDK__ and __ANDROID_VENDOR__ should be aliases"
+#endif
+
 Stability::Level Stability::getLocalLevel() {
 #ifdef __ANDROID_APEX__
 #error "APEX can't use libbinder (must use libbinder_ndk)"
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
index f178027..8d2944a 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -31,7 +31,11 @@
  */
 class PersistableBundle {
    public:
-    PersistableBundle() noexcept : mPBundle(APersistableBundle_new()) {}
+    PersistableBundle() noexcept {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            mPBundle = APersistableBundle_new();
+        }
+    }
     // takes ownership of the APersistableBundle*
     PersistableBundle(APersistableBundle* _Nonnull bundle) noexcept : mPBundle(bundle) {}
     // takes ownership of the APersistableBundle*
@@ -327,20 +331,32 @@
     }
 
     bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) {
-        return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
-                                    vec);
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
+                                        vec);
+        }
+        return false;
     }
     bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) {
-        return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
                                        vec);
+        }
+        return false;
     }
     bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) {
-        return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
                                        vec);
+        }
+        return false;
     }
     bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) {
-        return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle, key.c_str(),
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle, key.c_str(),
                                       vec);
+        }
+        return false;
     }
 
     // Takes ownership of and frees the char** and its elements.
@@ -361,15 +377,17 @@
     }
 
     bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) {
-        int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
-                                                           &stringAllocator, nullptr);
-        if (bytes > 0) {
-            char** strings = (char**)malloc(bytes);
-            if (strings) {
-                bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), strings, bytes,
-                                                           &stringAllocator, nullptr);
-                *vec = moveStringsInternal<std::vector<std::string>>(strings, bytes);
-                return true;
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
+                                                            &stringAllocator, nullptr);
+            if (bytes > 0) {
+                char** strings = (char**)malloc(bytes);
+                if (strings) {
+                    bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), strings, bytes,
+                                                            &stringAllocator, nullptr);
+                    *vec = moveStringsInternal<std::vector<std::string>>(strings, bytes);
+                    return true;
+                }
             }
         }
         return false;
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index e4d4de8..dd2be94 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -256,6 +256,12 @@
     // contention on the device. b/276820894
     test_options: {
         unit_test: false,
+        test_runner_options: [
+            {
+                name: "native-test-timeout",
+                value: "10m",
+            },
+        ],
     },
 
     test_suites: ["general-tests"],
@@ -830,6 +836,7 @@
         ],
         // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
         hotlists: ["4637097"],
+        use_for_presubmit: true,
     },
 }
 
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
index 788184d..5cbe0af 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -69,6 +69,7 @@
 	-DBINDER_DISABLE_NATIVE_HANDLE \
 	-DBINDER_DISABLE_BLOB \
 	-DBINDER_NO_LIBBASE \
+	-D__ANDROID_VENDOR__ \
 	-D__ANDROID_VNDK__ \
 
 MODULE_DEPS += \
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index e0f821f..f2f140d 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -72,6 +72,7 @@
 	-DBINDER_DISABLE_NATIVE_HANDLE \
 	-DBINDER_DISABLE_BLOB \
 	-DBINDER_NO_LIBBASE \
+	-D__ANDROID_VENDOR__ \
 	-D__ANDROID_VNDK__ \
 
 # libbinder has some deprecated declarations that we want to produce warnings
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 44a9e52..eb4d3df 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -423,7 +423,6 @@
         "libhidlbase",
         "liblog",
         "libnativewindow",
-        "libselinux",
         "libsync",
         "libui",
         "libutils",
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 744201a..11f5174 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -38,43 +38,10 @@
 #include <private/gui/BufferQueueThreadState.h>
 #if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
 #include <binder/PermissionCache.h>
-#include <selinux/android.h>
-#include <selinux/selinux.h>
 #endif
 
 #include <system/window.h>
 
-namespace {
-#if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
-int selinux_log_suppress_callback(int, const char*, ...) { // NOLINT
-    // DO NOTHING
-    return 0;
-}
-
-bool hasAccessToPermissionService() {
-    char* ctx;
-
-    if (getcon(&ctx) == -1) {
-        // Failed to get current selinux context
-        return false;
-    }
-
-    union selinux_callback cb;
-
-    cb.func_log = selinux_log_suppress_callback;
-    selinux_set_callback(SELINUX_CB_LOG, cb);
-
-    bool hasAccess = selinux_check_access(ctx, "u:object_r:permission_service:s0",
-                                          "service_manager", "find", NULL) == 0;
-    freecon(ctx);
-    cb.func_log = hasAccess ? selinux_log_callback : selinux_vendor_log_callback;
-    selinux_set_callback(SELINUX_CB_LOG, cb);
-
-    return hasAccess;
-}
-#endif
-} // namespace
-
 namespace android {
 
 // Macros for include BufferQueueCore information in log messages
@@ -843,18 +810,14 @@
     const uid_t uid = BufferQueueThreadState::getCallingUid();
 #if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
     // permission check can't be done for vendors as vendors have no access to
-    // the PermissionController. We need to do a runtime check as well, since
-    // the system variant of libgui can be loaded in a vendor process. For eg:
-    // if a HAL uses an llndk library that depends on libgui (libmediandk etc).
-    if (hasAccessToPermissionService()) {
-        const pid_t pid = BufferQueueThreadState::getCallingPid();
-        if ((uid != shellUid) &&
-            !PermissionCache::checkPermission(String16("android.permission.DUMP"), pid, uid)) {
-            outResult->appendFormat("Permission Denial: can't dump BufferQueueConsumer "
-                                    "from pid=%d, uid=%d\n",
-                                    pid, uid);
-            denied = true;
-        }
+    // the PermissionController.
+    const pid_t pid = BufferQueueThreadState::getCallingPid();
+    if ((uid != shellUid) &&
+        !PermissionCache::checkPermission(String16("android.permission.DUMP"), pid, uid)) {
+        outResult->appendFormat("Permission Denial: can't dump BufferQueueConsumer "
+                                "from pid=%d, uid=%d\n",
+                                pid, uid);
+        denied = true;
     }
 #else
     if (uid != shellUid) {
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 93df124..c6f3363 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -344,6 +344,10 @@
     handleRefreshRateUpdates();
 }
 
+void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId, int32_t, int32_t) {
+    LOG_ALWAYS_FATAL("dispatchHdcpLevelsChanged was called but was never registered");
+}
+
 void Choreographer::handleMessage(const Message& message) {
     switch (message.what) {
         case MSG_SCHEDULE_CALLBACKS:
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 5dd058c..f3de96d 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -195,6 +195,11 @@
                     dispatchFrameRateOverrides(ev.header.timestamp, ev.header.displayId,
                                                std::move(mFrameRateOverrides));
                     break;
+                case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+                    dispatchHdcpLevelsChanged(ev.header.displayId,
+                                              ev.hdcpLevelsChange.connectedLevel,
+                                              ev.hdcpLevelsChange.maxLevel);
+                    break;
                 default:
                     ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
                     break;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 8b6f202..8d18551 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -3117,7 +3117,6 @@
             ->removeWindowInfosListener(windowInfosListener,
                                         ComposerServiceAIDL::getComposerService());
 }
-
 // ----------------------------------------------------------------------------
 
 status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 9933680..c952ba2 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -207,6 +207,7 @@
     MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId));
     MOCK_METHOD3(dispatchFrameRateOverrides,
                  void(nsecs_t, PhysicalDisplayId, std::vector<FrameRateOverride>));
+    MOCK_METHOD3(dispatchHdcpLevelsChanged, void(PhysicalDisplayId, int32_t, int32_t));
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 9fef512..55a7aa7 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -116,6 +116,8 @@
     void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
+    void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                   int32_t maxLevel) override;
 
     void scheduleCallbacks();
 
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index fe2dd20..82cd50c 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -65,6 +65,9 @@
     virtual void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                             std::vector<FrameRateOverride> overrides) = 0;
 
+    virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                           int32_t maxLevel) = 0;
+
     bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
                               uint32_t* outCount, VsyncEventData* outVsyncEventData);
 
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 79582ce..8c1103b 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -58,7 +58,6 @@
 // ----------------------------------------------------------------------------
 class DisplayEventReceiver {
 public:
-
     enum {
         DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
         DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
@@ -66,6 +65,7 @@
         DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
+        DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'),
     };
 
     struct Event {
@@ -101,12 +101,22 @@
             float frameRateHz __attribute__((aligned(8)));
         };
 
+        /*
+         * The values are defined in aidl:
+         * hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl
+         */
+        struct HdcpLevelsChange {
+            int32_t connectedLevel;
+            int32_t maxLevel;
+        };
+
         Header header;
         union {
             VSync vsync;
             Hotplug hotplug;
             ModeChange modeChange;
             FrameRateOverride frameRateOverride;
+            HdcpLevelsChange hdcpLevelsChange;
         };
     };
 
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 4d4c5e4..b72b71a 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -176,6 +176,8 @@
                 static_cast<uint32_t>(os::InputConfig::INTERCEPTS_STYLUS),
         CLONE =
                 static_cast<uint32_t>(os::InputConfig::CLONE),
+        GLOBAL_STYLUS_BLOCKS_TOUCH =
+                static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH),
         // clang-format on
     };
 
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index bd5b67b..8eaff00 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -379,6 +379,11 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const PointerProperties& properties) {
+    out << "Pointer(id=" << properties.id << ", " << ftl::enum_string(properties.toolType) << ")";
+    return out;
+}
+
 // --- PointerCoords ---
 
 float PointerCoords::getAxisValue(int32_t axis) const {
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 09e98d0..669f801 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -646,7 +646,7 @@
               "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
               "metaState=0x%x, buttonState=0x%x, classification=%s,"
               "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
-              "pointerCount=%" PRIu32 " \n%s",
+              "pointerCount=%" PRIu32 "\n%s",
               mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
               inputEventSourceToString(source).c_str(), displayId,
               MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags,
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index 4e644ff..5d39155 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -150,4 +150,11 @@
      * likely a duplicate window with the same client token, but different bounds.
      */
     CLONE                        = 1 << 16,
+
+    /**
+     * If the stylus is currently down *anywhere* on the screen, new touches will not be delivered
+     * to the window with this flag. This helps prevent unexpected clicks on some system windows,
+     * like StatusBar and TaskBar.
+     */
+    GLOBAL_STYLUS_BLOCKS_TOUCH   = 1 << 17,
 }
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 54eeb39..11f6994 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -52,7 +52,14 @@
 flag {
   name: "enable_touchpad_typing_palm_rejection"
   namespace: "input"
-  description: "Enable additional palm rejection on touchpad while typing"
+  description: "Enabling additional touchpad palm rejection will disable the tap to click while the user is typing on a physical keyboard"
+  bug: "301055381"
+}
+
+flag {
+  name: "enable_v2_touchpad_typing_palm_rejection"
+  namespace: "input"
+  description: "In addition to touchpad palm rejection v1, v2 will also cancel ongoing move gestures while typing and add delay in re-enabling the tap to click."
   bug: "301055381"
 }
 
diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
index 0d5727d..68ac7e0 100644
--- a/libs/nativewindow/include/android/native_window_aidl.h
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -103,14 +103,22 @@
 
     binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
         reset();
-        return ANativeWindow_readFromParcel(parcel, &mWindow);
+        if (__builtin_available(android __ANDROID_API_U__, *)) {
+            return ANativeWindow_readFromParcel(parcel, &mWindow);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
     }
 
     binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
         if (!mWindow) {
             return STATUS_BAD_VALUE;
         }
-        return ANativeWindow_writeToParcel(mWindow, parcel);
+        if (__builtin_available(android __ANDROID_API_U__, *)) {
+            return ANativeWindow_writeToParcel(mWindow, parcel);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
     }
 
     /**
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index d112a12..f8ee3fc 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -248,6 +248,22 @@
     return static_cast<ssize_t>(mSensors.size());
 }
 
+ssize_t SensorManager::getDefaultDeviceSensorList(Vector<Sensor> & list) {
+    Mutex::Autolock _l(mLock);
+    status_t err = assertStateLocked();
+    if (err < 0) {
+        return static_cast<ssize_t>(err);
+    }
+
+    if (mDeviceId == DEVICE_ID_DEFAULT) {
+        list = mSensors;
+    } else {
+        list = mSensorServer->getSensorList(mOpPackageName);
+    }
+
+    return static_cast<ssize_t>(list.size());
+}
+
 ssize_t SensorManager::getDynamicSensorList(Vector<Sensor> & dynamicSensors) {
     Mutex::Autolock _l(mLock);
     status_t err = assertStateLocked();
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index e67fac7..49f050a 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -58,6 +58,7 @@
     ~SensorManager();
 
     ssize_t getSensorList(Sensor const* const** list);
+    ssize_t getDefaultDeviceSensorList(Vector<Sensor> & list);
     ssize_t getDynamicSensorList(Vector<Sensor>& list);
     ssize_t getDynamicSensorList(Sensor const* const** list);
     ssize_t getRuntimeSensorList(int deviceId, Vector<Sensor>& list);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 6033398..112f16b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -72,7 +72,8 @@
 using android::os::InputEventInjectionSync;
 namespace input_flags = com::android::input::flags;
 
-static const bool REMOVE_APP_SWITCH_DROPS = input_flags::remove_app_switch_drops();
+// TODO(b/312714754): remove the corresponding code, as well.
+static const bool REMOVE_APP_SWITCH_DROPS = true;
 
 namespace android::inputdispatcher {
 
@@ -252,6 +253,14 @@
     }
 }
 
+std::bitset<MAX_POINTER_ID + 1> getPointerIds(const std::vector<PointerProperties>& pointers) {
+    std::bitset<MAX_POINTER_ID + 1> pointerIds;
+    for (const PointerProperties& pointer : pointers) {
+        pointerIds.set(pointer.id);
+    }
+    return pointerIds;
+}
+
 std::string dumpRegion(const Region& region) {
     if (region.isEmpty()) {
         return "<empty>";
@@ -630,15 +639,15 @@
     }
 
     // We should consider all hovering pointers here. But for now, just use the first one
-    const int32_t pointerId = entry.pointerProperties[0].id;
+    const PointerProperties& pointer = entry.pointerProperties[0];
 
     std::set<sp<WindowInfoHandle>> oldWindows;
     if (oldState != nullptr) {
-        oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+        oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointer.id);
     }
 
     std::set<sp<WindowInfoHandle>> newWindows =
-            newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+            newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointer.id);
 
     // If the pointer is no longer in the new window set, send HOVER_EXIT.
     for (const sp<WindowInfoHandle>& oldWindow : oldWindows) {
@@ -672,7 +681,7 @@
             }
             touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
         }
-        touchedWindow.addHoveringPointer(entry.deviceId, pointerId);
+        touchedWindow.addHoveringPointer(entry.deviceId, pointer);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
             touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
         }
@@ -740,6 +749,20 @@
     return true;
 }
 
+/**
+ * Return true if stylus is currently down anywhere on the specified display, and false otherwise.
+ */
+bool isStylusActiveInDisplay(
+        int32_t displayId,
+        const std::unordered_map<int32_t /*displayId*/, TouchState>& touchStatesByDisplay) {
+    const auto it = touchStatesByDisplay.find(displayId);
+    if (it == touchStatesByDisplay.end()) {
+        return false;
+    }
+    const TouchState& state = it->second;
+    return state.hasActiveStylus();
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -2135,7 +2158,6 @@
 sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
         nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
         InputEventInjectionResult& outInjectionResult) {
-    std::string reason;
     outInjectionResult = InputEventInjectionResult::FAILED; // Default result
 
     int32_t displayId = getTargetDisplayId(entry);
@@ -2320,7 +2342,7 @@
         /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
         const auto [x, y] = resolveTouchedPosition(entry);
         const int32_t pointerIndex = MotionEvent::getActionIndex(action);
-        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
+        const PointerProperties& pointer = entry.pointerProperties[pointerIndex];
         // Outside targets should be added upon first dispatched DOWN event. That means, this should
         // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
@@ -2328,7 +2350,7 @@
                 findTouchedWindowAtLocked(displayId, x, y, isStylus);
 
         if (isDown) {
-            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId);
+            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointer.id);
         }
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
@@ -2386,7 +2408,7 @@
 
             if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
-                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
+                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer);
             }
 
             // Set target flags.
@@ -2409,12 +2431,10 @@
             // Update the temporary touch state.
 
             if (!isHoverAction) {
-                std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                pointerIds.set(pointerId);
                 const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                         maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
                 tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS,
-                                                 targetFlags, entry.deviceId, pointerIds,
+                                                 targetFlags, entry.deviceId, {pointer},
                                                  isDownOrPointerDown
                                                          ? std::make_optional(entry.eventTime)
                                                          : std::nullopt);
@@ -2437,7 +2457,7 @@
                         }
                         tempTouchState.addOrUpdateWindow(wallpaper,
                                                          InputTarget::DispatchMode::AS_IS,
-                                                         wallpaperFlags, entry.deviceId, pointerIds,
+                                                         wallpaperFlags, entry.deviceId, {pointer},
                                                          entry.eventTime);
                     }
                 }
@@ -2448,12 +2468,12 @@
         // make it pilfering. This will prevent other non-spy windows from getting this pointer,
         // which is a specific behaviour that we want.
         for (TouchedWindow& touchedWindow : tempTouchState.windows) {
-            if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
+            if (touchedWindow.hasTouchingPointer(entry.deviceId, pointer.id) &&
                 touchedWindow.hasPilferingPointers(entry.deviceId)) {
                 // This window is already pilfering some pointers, and this new pointer is also
                 // going to it. Therefore, take over this pointer and don't give it to anyone
                 // else.
-                touchedWindow.addPilferingPointer(entry.deviceId, pointerId);
+                touchedWindow.addPilferingPointer(entry.deviceId, pointer.id);
             }
         }
 
@@ -2522,8 +2542,8 @@
 
                 // Make a slippery exit from the old window.
                 std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                const int32_t pointerId = entry.pointerProperties[0].id;
-                pointerIds.set(pointerId);
+                const PointerProperties& pointer = entry.pointerProperties[0];
+                pointerIds.set(pointer.id);
 
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
@@ -2553,13 +2573,13 @@
 
                 tempTouchState.addOrUpdateWindow(newTouchedWindowHandle,
                                                  InputTarget::DispatchMode::SLIPPERY_ENTER,
-                                                 targetFlags, entry.deviceId, pointerIds,
+                                                 targetFlags, entry.deviceId, {pointer},
                                                  entry.eventTime);
 
                 // Check if the wallpaper window should deliver the corresponding event.
                 slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
-                                   tempTouchState, entry.deviceId, pointerId, targets);
-                tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointerId,
+                                   tempTouchState, entry.deviceId, pointer, targets);
+                tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointer.id,
                                                                oldTouchedWindowHandle);
             }
         }
@@ -2568,14 +2588,12 @@
         if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
             // If no split, we suppose all touched windows should receive pointer down.
             const int32_t pointerIndex = MotionEvent::getActionIndex(action);
-            for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
-                TouchedWindow& touchedWindow = tempTouchState.windows[i];
+            std::vector<PointerProperties> touchingPointers{entry.pointerProperties[pointerIndex]};
+            for (TouchedWindow& touchedWindow : tempTouchState.windows) {
                 // Ignore drag window for it should just track one pointer.
                 if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
                     continue;
                 }
-                std::bitset<MAX_POINTER_ID + 1> touchingPointers;
-                touchingPointers.set(entry.pointerProperties[pointerIndex].id);
                 touchedWindow.addTouchingPointers(entry.deviceId, touchingPointers);
             }
         }
@@ -2646,13 +2664,13 @@
 
     // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-        std::bitset<MAX_POINTER_ID + 1> touchingPointers =
+        std::vector<PointerProperties> touchingPointers =
                 touchedWindow.getTouchingPointers(entry.deviceId);
-        if (touchingPointers.none()) {
+        if (touchingPointers.empty()) {
             continue;
         }
         addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
-                                     touchedWindow.targetFlags, touchingPointers,
+                                     touchedWindow.targetFlags, getPointerIds(touchingPointers),
                                      touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
     }
 
@@ -3371,27 +3389,40 @@
 
                 dispatchEntry->resolvedFlags = resolvedFlags;
                 if (resolvedAction != motionEntry.action) {
+                    std::optional<std::vector<PointerProperties>> usingProperties;
+                    std::optional<std::vector<PointerCoords>> usingCoords;
+                    if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT ||
+                        resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
+                        // This is a HOVER_EXIT or an ACTION_CANCEL event that was synthesized by
+                        // the dispatcher, and therefore the coordinates of this event are currently
+                        // incorrect. These events should use the coordinates of the last dispatched
+                        // ACTION_MOVE or HOVER_MOVE. We need to query InputState to get this data.
+                        const bool hovering = resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT;
+                        std::optional<std::pair<std::vector<PointerProperties>,
+                                                std::vector<PointerCoords>>>
+                                pointerInfo =
+                                        connection->inputState.getPointersOfLastEvent(motionEntry,
+                                                                                      hovering);
+                        if (pointerInfo) {
+                            usingProperties = pointerInfo->first;
+                            usingCoords = pointerInfo->second;
+                        }
+                    }
                     // Generate a new MotionEntry with a new eventId using the resolved action and
                     // flags.
-                    resolvedMotion =
-                            std::make_shared<MotionEntry>(mIdGenerator.nextId(),
-                                                          motionEntry.injectionState,
-                                                          motionEntry.eventTime,
-                                                          motionEntry.deviceId, motionEntry.source,
-                                                          motionEntry.displayId,
-                                                          motionEntry.policyFlags, resolvedAction,
-                                                          motionEntry.actionButton, resolvedFlags,
-                                                          motionEntry.metaState,
-                                                          motionEntry.buttonState,
-                                                          motionEntry.classification,
-                                                          motionEntry.edgeFlags,
-                                                          motionEntry.xPrecision,
-                                                          motionEntry.yPrecision,
-                                                          motionEntry.xCursorPosition,
-                                                          motionEntry.yCursorPosition,
-                                                          motionEntry.downTime,
-                                                          motionEntry.pointerProperties,
-                                                          motionEntry.pointerCoords);
+                    resolvedMotion = std::make_shared<
+                            MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState,
+                                         motionEntry.eventTime, motionEntry.deviceId,
+                                         motionEntry.source, motionEntry.displayId,
+                                         motionEntry.policyFlags, resolvedAction,
+                                         motionEntry.actionButton, resolvedFlags,
+                                         motionEntry.metaState, motionEntry.buttonState,
+                                         motionEntry.classification, motionEntry.edgeFlags,
+                                         motionEntry.xPrecision, motionEntry.yPrecision,
+                                         motionEntry.xCursorPosition, motionEntry.yCursorPosition,
+                                         motionEntry.downTime,
+                                         usingProperties.value_or(motionEntry.pointerProperties),
+                                         usingCoords.value_or(motionEntry.pointerCoords));
                     if (ATRACE_ENABLED()) {
                         std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
                                                            ") to MotionEvent(id=0x%" PRIx32 ").",
@@ -4418,7 +4449,8 @@
     policyFlags |= POLICY_FLAG_TRUSTED;
 
     android::base::Timer t;
-    mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags);
+    mPolicy.interceptMotionBeforeQueueing(args.displayId, args.source, args.action, args.eventTime,
+                                          policyFlags);
     if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
         ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
               std::to_string(t.duration().count()).c_str());
@@ -4679,7 +4711,9 @@
             if (!(policyFlags & POLICY_FLAG_FILTERED)) {
                 nsecs_t eventTime = motionEvent.getEventTime();
                 android::base::Timer t;
-                mPolicy.interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags);
+                mPolicy.interceptMotionBeforeQueueing(displayId, motionEvent.getSource(),
+                                                      motionEvent.getAction(), eventTime,
+                                                      /*byref*/ policyFlags);
                 if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
                     ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
                           std::to_string(t.duration().count()).c_str());
@@ -5047,6 +5081,13 @@
         return false;
     }
 
+    // Ignore touches if stylus is down anywhere on screen
+    if (info.inputConfig.test(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH) &&
+        isStylusActiveInDisplay(info.displayId, mTouchStatesByDisplay)) {
+        LOG(INFO) << "Dropping touch from " << window->getName() << " because stylus is active";
+        return false;
+    }
+
     return true;
 }
 
@@ -5481,7 +5522,7 @@
 
         // Erase old window.
         ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
-        std::bitset<MAX_POINTER_ID + 1> pointerIds = touchedWindow->getTouchingPointers(deviceId);
+        std::vector<PointerProperties> pointers = touchedWindow->getTouchingPointers(deviceId);
         sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
         state->removeWindowByToken(fromToken);
 
@@ -5493,17 +5534,17 @@
             newTargetFlags |= InputTarget::Flags::FOREGROUND;
         }
         state->addOrUpdateWindow(toWindowHandle, InputTarget::DispatchMode::AS_IS, newTargetFlags,
-                                 deviceId, pointerIds, downTimeInTarget);
+                                 deviceId, pointers, downTimeInTarget);
 
         // Store the dragging window.
         if (isDragDrop) {
-            if (pointerIds.count() != 1) {
+            if (pointers.size() != 1) {
                 ALOGW("The drag and drop cannot be started when there is no pointer or more than 1"
                       " pointer on the window.");
                 return false;
             }
             // Track the pointer id for drag window and generate the drag state.
-            const size_t id = firstMarkedBit(pointerIds);
+            const size_t id = pointers.begin()->id;
             mDragState = std::make_unique<DragState>(toWindowHandle, id);
         }
 
@@ -5520,7 +5561,7 @@
 
             // Check if the wallpaper window should deliver the corresponding event.
             transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
-                                   *state, deviceId, pointerIds);
+                                   *state, deviceId, pointers);
         }
     } // release lock
 
@@ -5997,8 +6038,10 @@
                                    "input channel stole pointer stream");
         options.deviceId = deviceId;
         options.displayId = displayId;
-        std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
+        std::vector<PointerProperties> pointers = window.getTouchingPointers(deviceId);
+        std::bitset<MAX_POINTER_ID + 1> pointerIds = getPointerIds(pointers);
         options.pointerIds = pointerIds;
+
         std::string canceledWindows;
         for (const TouchedWindow& w : state.windows) {
             const std::shared_ptr<InputChannel> channel =
@@ -6794,10 +6837,10 @@
 void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
-                                         TouchState& state, int32_t deviceId, int32_t pointerId,
+                                         TouchState& state, int32_t deviceId,
+                                         const PointerProperties& pointerProperties,
                                          std::vector<InputTarget>& targets) const {
-    std::bitset<MAX_POINTER_ID + 1> pointerIds;
-    pointerIds.set(pointerId);
+    std::vector<PointerProperties> pointers{pointerProperties};
     const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
     const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
@@ -6814,16 +6857,16 @@
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
         addPointerWindowTargetLocked(oldWallpaper, InputTarget::DispatchMode::SLIPPERY_EXIT,
-                                     oldTouchedWindow.targetFlags, pointerIds,
+                                     oldTouchedWindow.targetFlags, getPointerIds(pointers),
                                      oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
-        state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
+        state.removeTouchingPointerFromWindow(deviceId, pointerProperties.id, oldWallpaper);
     }
 
     if (newWallpaper != nullptr) {
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER,
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
                                         InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
-                                deviceId, pointerIds);
+                                deviceId, pointers);
     }
 }
 
@@ -6832,7 +6875,7 @@
                                              const sp<WindowInfoHandle> fromWindowHandle,
                                              const sp<WindowInfoHandle> toWindowHandle,
                                              TouchState& state, int32_t deviceId,
-                                             std::bitset<MAX_POINTER_ID + 1> pointerIds) {
+                                             const std::vector<PointerProperties>& pointers) {
     const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
             fromWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -6861,7 +6904,7 @@
         wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED |
                 InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::AS_IS, wallpaperFlags,
-                                deviceId, pointerIds, downTimeInTarget);
+                                deviceId, pointers, downTimeInTarget);
         std::shared_ptr<Connection> wallpaperConnection =
                 getConnectionLocked(newWallpaper->getToken());
         if (wallpaperConnection != nullptr) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 3f99b2d..010dbb2 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -690,14 +690,15 @@
     void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                             const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
                             const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                            TouchState& state, int32_t deviceId, int32_t pointerId,
+                            TouchState& state, int32_t deviceId,
+                            const PointerProperties& pointerProperties,
                             std::vector<InputTarget>& targets) const REQUIRES(mLock);
     void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                                 const sp<android::gui::WindowInfoHandle> fromWindowHandle,
                                 const sp<android::gui::WindowInfoHandle> toWindowHandle,
                                 TouchState& state, int32_t deviceId,
-                                std::bitset<MAX_POINTER_ID + 1> pointerIds) REQUIRES(mLock);
+                                const std::vector<PointerProperties>& pointers) REQUIRES(mLock);
 
     sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index a4ac4fb..1fec9b7 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -195,6 +195,16 @@
     }
 }
 
+std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>>
+InputState::getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const {
+    ssize_t index = findMotionMemento(entry, hovering);
+    if (index == -1) {
+        return std::nullopt;
+    }
+    return std::make_pair(mMotionMementos[index].pointerProperties,
+                          mMotionMementos[index].pointerCoords);
+}
+
 ssize_t InputState::findKeyMemento(const KeyEntry& entry) const {
     for (size_t i = 0; i < mKeyMementos.size(); i++) {
         const KeyMemento& memento = mKeyMementos[i];
@@ -311,16 +321,6 @@
             return true;
         }
 
-        // Use the previous stream cancellation logic to generate all HOVER_EXIT events.
-        // If this hover event was generated as a result of the pointer leaving the window,
-        // the HOVER_EXIT event should have the same coordinates as the previous
-        // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same
-        // coordinates as the previous event by cancelling the stream here. With this approach, the
-        // HOVER_EXIT event is generated from the previous event.
-        if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) {
-            return true;
-        }
-
         // If the stream changes its source, just cancel the current gesture to be safe. It's
         // possible that the app isn't handling source changes properly
         if (motionEntry.source != lastMemento.source) {
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index b0e4209..d49469d 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -48,6 +48,15 @@
     // and should be skipped.
     bool trackMotion(const MotionEntry& entry, int32_t flags);
 
+    /**
+     * Return the PointerProperties and the PointerCoords for the last event, if found. Return
+     * std::nullopt if not found. We should not return std::vector<PointerCoords> in isolation,
+     * because the pointers can technically be stored in the vector in any order, so the
+     * PointerProperties are needed to specify the order in which the pointer coords are stored.
+     */
+    std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>>
+    getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const;
+
     // Create cancel events for the previous stream if the current motionEntry requires it.
     std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry);
 
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index e8d8c18..f8aa625 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -73,9 +73,9 @@
 void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
                                    InputTarget::DispatchMode dispatchMode,
                                    ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                                   std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
+                                   const std::vector<PointerProperties>& touchingPointers,
                                    std::optional<nsecs_t> firstDownTimeInTarget) {
-    if (touchingPointerIds.none()) {
+    if (touchingPointers.empty()) {
         LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
         return;
     }
@@ -91,7 +91,7 @@
             // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
             // downTime set initially. Need to update existing window when a pointer is down for the
             // window.
-            touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+            touchedWindow.addTouchingPointers(deviceId, touchingPointers);
             if (firstDownTimeInTarget) {
                 touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
             }
@@ -102,7 +102,7 @@
     touchedWindow.windowHandle = windowHandle;
     touchedWindow.dispatchMode = dispatchMode;
     touchedWindow.targetFlags = targetFlags;
-    touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+    touchedWindow.addTouchingPointers(deviceId, touchingPointers);
     if (firstDownTimeInTarget) {
         touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
     }
@@ -110,17 +110,17 @@
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
-                                            DeviceId deviceId, int32_t hoveringPointerId) {
+                                            DeviceId deviceId, const PointerProperties& pointer) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
+            touchedWindow.addHoveringPointer(deviceId, pointer);
             return;
         }
     }
 
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
-    touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
+    touchedWindow.addHoveringPointer(deviceId, pointer);
     windows.push_back(touchedWindow);
 }
 
@@ -234,6 +234,11 @@
     });
 }
 
+bool TouchState::hasActiveStylus() const {
+    return std::any_of(windows.begin(), windows.end(),
+                       [](const TouchedWindow& window) { return window.hasActiveStylus(); });
+}
+
 std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId,
                                                                          int32_t pointerId) const {
     std::set<sp<WindowInfoHandle>> out;
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index e0a84e8..3d534bc 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -46,11 +46,11 @@
     void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                            InputTarget::DispatchMode dispatchMode,
                            ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                           std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
+                           const std::vector<PointerProperties>& touchingPointers,
                            std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                    DeviceId deviceId, int32_t hoveringPointerId);
-    void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId);
+                                    DeviceId deviceId, const PointerProperties& pointer);
+    void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
     void clearHoveringPointers(DeviceId deviceId);
 
     void removeAllPointersForDevice(DeviceId deviceId);
@@ -73,6 +73,8 @@
     bool isDown(DeviceId deviceId) const;
     bool hasHoveringPointers(DeviceId deviceId) const;
 
+    bool hasActiveStylus() const;
+
     std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
             DeviceId deviceId, int32_t pointerId) const;
     std::string dump() const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index cd0500c..037d7c8 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -26,9 +26,20 @@
 
 namespace inputdispatcher {
 
+namespace {
+
+bool hasPointerId(const std::vector<PointerProperties>& pointers, int32_t pointerId) {
+    return std::find_if(pointers.begin(), pointers.end(),
+                        [&pointerId](const PointerProperties& properties) {
+                            return properties.id == pointerId;
+                        }) != pointers.end();
+}
+
+} // namespace
+
 bool TouchedWindow::hasHoveringPointers() const {
     for (const auto& [_, state] : mDeviceStates) {
-        if (state.hoveringPointerIds.any()) {
+        if (!state.hoveringPointers.empty()) {
             return true;
         }
     }
@@ -42,7 +53,7 @@
     }
     const DeviceState& state = stateIt->second;
 
-    return state.hoveringPointerIds.any();
+    return !state.hoveringPointers.empty();
 }
 
 void TouchedWindow::clearHoveringPointers(DeviceId deviceId) {
@@ -51,7 +62,7 @@
         return;
     }
     DeviceState& state = stateIt->second;
-    state.hoveringPointerIds.reset();
+    state.hoveringPointers.clear();
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
     }
@@ -63,22 +74,40 @@
         return false;
     }
     const DeviceState& state = stateIt->second;
-
-    return state.hoveringPointerIds.test(pointerId);
+    return hasPointerId(state.hoveringPointers, pointerId);
 }
 
-void TouchedWindow::addHoveringPointer(DeviceId deviceId, int32_t pointerId) {
-    mDeviceStates[deviceId].hoveringPointerIds.set(pointerId);
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) {
+    std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
+    const size_t initialSize = hoveringPointers.size();
+    std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) {
+        return properties.id == pointer.id;
+    });
+    if (hoveringPointers.size() != initialSize) {
+        LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this;
+    }
+    hoveringPointers.push_back(pointer);
 }
 
 void TouchedWindow::addTouchingPointers(DeviceId deviceId,
-                                        std::bitset<MAX_POINTER_ID + 1> pointers) {
-    mDeviceStates[deviceId].touchingPointerIds |= pointers;
+                                        const std::vector<PointerProperties>& pointers) {
+    std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers;
+    const size_t initialSize = touchingPointers.size();
+    for (const PointerProperties& pointer : pointers) {
+        std::erase_if(touchingPointers, [&pointer](const PointerProperties& properties) {
+            return properties.id == pointer.id;
+        });
+    }
+    if (touchingPointers.size() != initialSize) {
+        LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device "
+                   << deviceId << " already in " << *this;
+    }
+    touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end());
 }
 
 bool TouchedWindow::hasTouchingPointers() const {
     for (const auto& [_, state] : mDeviceStates) {
-        if (state.touchingPointerIds.any()) {
+        if (!state.touchingPointers.empty()) {
             return true;
         }
     }
@@ -86,21 +115,25 @@
 }
 
 bool TouchedWindow::hasTouchingPointers(DeviceId deviceId) const {
-    return getTouchingPointers(deviceId).any();
+    return !getTouchingPointers(deviceId).empty();
 }
 
 bool TouchedWindow::hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const {
-    return getTouchingPointers(deviceId).test(pointerId);
+    const auto stateIt = mDeviceStates.find(deviceId);
+    if (stateIt == mDeviceStates.end()) {
+        return false;
+    }
+    const DeviceState& state = stateIt->second;
+    return hasPointerId(state.touchingPointers, pointerId);
 }
 
-std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
+std::vector<PointerProperties> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return {};
     }
     const DeviceState& state = stateIt->second;
-
-    return state.touchingPointerIds;
+    return state.touchingPointers;
 }
 
 void TouchedWindow::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) {
@@ -118,7 +151,10 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.touchingPointerIds &= ~pointers;
+    std::erase_if(state.touchingPointers, [&pointers](const PointerProperties& properties) {
+        return pointers.test(properties.id);
+    });
+
     state.pilferingPointerIds &= ~pointers;
 
     if (!state.hasPointers()) {
@@ -126,10 +162,26 @@
     }
 }
 
+bool TouchedWindow::hasActiveStylus() const {
+    for (const auto& [_, state] : mDeviceStates) {
+        for (const PointerProperties& properties : state.touchingPointers) {
+            if (properties.toolType == ToolType::STYLUS) {
+                return true;
+            }
+        }
+        for (const PointerProperties& properties : state.hoveringPointers) {
+            if (properties.toolType == ToolType::STYLUS) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const {
     std::set<DeviceId> deviceIds;
     for (const auto& [deviceId, deviceState] : mDeviceStates) {
-        if (deviceState.touchingPointerIds.any()) {
+        if (!deviceState.touchingPointers.empty()) {
             deviceIds.insert(deviceId);
         }
     }
@@ -198,7 +250,7 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.touchingPointerIds.reset();
+    state.touchingPointers.clear();
     state.pilferingPointerIds.reset();
     state.downTimeInTarget.reset();
 
@@ -214,7 +266,9 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.hoveringPointerIds.set(pointerId, false);
+    std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) {
+        return properties.id == pointerId;
+    });
 
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
@@ -228,7 +282,7 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.hoveringPointerIds.reset();
+    state.hoveringPointers.clear();
 
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
@@ -236,11 +290,11 @@
 }
 
 std::string TouchedWindow::deviceStateToString(const TouchedWindow::DeviceState& state) {
-    return StringPrintf("[touchingPointerIds=%s, "
-                        "downTimeInTarget=%s, hoveringPointerIds=%s, pilferingPointerIds=%s]",
-                        bitsetToString(state.touchingPointerIds).c_str(),
+    return StringPrintf("[touchingPointers=%s, "
+                        "downTimeInTarget=%s, hoveringPointers=%s, pilferingPointerIds=%s]",
+                        dumpVector(state.touchingPointers, streamableToString).c_str(),
                         toString(state.downTimeInTarget).c_str(),
-                        bitsetToString(state.hoveringPointerIds).c_str(),
+                        dumpVector(state.hoveringPointers, streamableToString).c_str(),
                         bitsetToString(state.pilferingPointerIds).c_str());
 }
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index c604353..0d1531f 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -38,17 +38,18 @@
     bool hasHoveringPointers() const;
     bool hasHoveringPointers(DeviceId deviceId) const;
     bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
-    void addHoveringPointer(DeviceId deviceId, int32_t pointerId);
+    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
 
     // Touching
     bool hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const;
     bool hasTouchingPointers() const;
     bool hasTouchingPointers(DeviceId deviceId) const;
-    std::bitset<MAX_POINTER_ID + 1> getTouchingPointers(DeviceId deviceId) const;
-    void addTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+    std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const;
+    void addTouchingPointers(DeviceId deviceId, const std::vector<PointerProperties>& pointers);
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+    bool hasActiveStylus() const;
     std::set<DeviceId> getTouchingDeviceIds() const;
 
     // Pilfering pointers
@@ -69,16 +70,16 @@
 
 private:
     struct DeviceState {
-        std::bitset<MAX_POINTER_ID + 1> touchingPointerIds;
+        std::vector<PointerProperties> touchingPointers;
         // The pointer ids of the pointers that this window is currently pilfering, by device
         std::bitset<MAX_POINTER_ID + 1> pilferingPointerIds;
         // Time at which the first action down occurred on this window, for each device
         // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
         // scenario.
         std::optional<nsecs_t> downTimeInTarget;
-        std::bitset<MAX_POINTER_ID + 1> hoveringPointerIds;
+        std::vector<PointerProperties> hoveringPointers;
 
-        bool hasPointers() const { return touchingPointerIds.any() || hoveringPointerIds.any(); };
+        bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); };
     };
 
     std::map<DeviceId, DeviceState> mDeviceStates;
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 1c23720..9e6209b 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -99,8 +99,8 @@
      * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
      * should be dispatched to applications.
      */
-    virtual void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
-                                               uint32_t& policyFlags) = 0;
+    virtual void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
+                                               nsecs_t when, uint32_t& policyFlags) = 0;
 
     /* Allows the policy a chance to intercept a key before dispatching. */
     virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token,
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 5a74a42..2dd05f5 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -35,10 +35,12 @@
 MultiTouchInputMapper::~MultiTouchInputMapper() {}
 
 std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) {
-    // TODO(b/291626046): Sync the MT state with the kernel using EVIOCGMTSLOTS.
-    mMultiTouchMotionAccumulator.reset(getDeviceContext());
-    mPointerIdBits.clear();
-
+    // The evdev multi-touch protocol does not allow userspace applications to query the initial or
+    // current state of the pointers at any time. This means if we clear our accumulated state when
+    // resetting the input mapper, there's no way to rebuild the full initial state of the pointers.
+    // We can only wait for updates to all the pointers and axes. Rather than clearing the state and
+    // rebuilding the state from scratch, we work around this kernel API limitation by never
+    // fully clearing any state specific to the multi-touch protocol.
     return TouchInputMapper::reset(when);
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 34ca0b3..255f02d 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -21,9 +21,11 @@
 #include <iterator>
 #include <limits>
 #include <map>
+#include <mutex>
 #include <optional>
 
 #include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
 #include <android/input.h>
 #include <com_android_input_flags.h>
 #include <ftl/enum.h>
@@ -156,13 +158,20 @@
         return sAccumulator;
     }
 
-    void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].fingers++; }
+    void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) {
+        std::scoped_lock lock(mLock);
+        mCounters[id].fingers++;
+    }
 
-    void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].palms++; }
+    void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) {
+        std::scoped_lock lock(mLock);
+        mCounters[id].palms++;
+    }
 
     // Checks whether a Gesture struct is for the end of a gesture that we log metrics for, and
     // records it if so.
     void processGesture(const TouchpadInputMapper::MetricsIdentifier& id, const Gesture& gesture) {
+        std::scoped_lock lock(mLock);
         switch (gesture.type) {
             case kGestureTypeFling:
                 if (gesture.details.fling.fling_state == GESTURES_FLING_START) {
@@ -200,15 +209,20 @@
                                                                  void* cookie) {
         LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE);
         MetricsAccumulator& accumulator = MetricsAccumulator::getInstance();
-        accumulator.produceAtoms(outEventList);
-        accumulator.resetCounters();
+        accumulator.produceAtomsAndReset(*outEventList);
         return AStatsManager_PULL_SUCCESS;
     }
 
-    void produceAtoms(AStatsEventList* outEventList) const {
+    void produceAtomsAndReset(AStatsEventList& outEventList) {
+        std::scoped_lock lock(mLock);
+        produceAtomsLocked(outEventList);
+        resetCountersLocked();
+    }
+
+    void produceAtomsLocked(AStatsEventList& outEventList) const REQUIRES(mLock) {
         for (auto& [id, counters] : mCounters) {
             auto [busId, vendorId, productId, versionId] = id;
-            addAStatsEvent(outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
+            addAStatsEvent(&outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
                            versionId, linuxBusToInputDeviceBusEnum(busId, /*isUsi=*/false),
                            counters.fingers, counters.palms, counters.twoFingerSwipeGestures,
                            counters.threeFingerSwipeGestures, counters.fourFingerSwipeGestures,
@@ -216,7 +230,7 @@
         }
     }
 
-    void resetCounters() { mCounters.clear(); }
+    void resetCountersLocked() REQUIRES(mLock) { mCounters.clear(); }
 
     // Stores the counters for a specific touchpad model. Fields have the same meanings as those of
     // the TouchpadUsage atom; see that definition for detailed documentation.
@@ -232,7 +246,10 @@
 
     // Metrics are aggregated by device model and version, so if two devices of the same model and
     // version are connected at once, they will have the same counters.
-    std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters;
+    std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters GUARDED_BY(mLock);
+
+    // Metrics are pulled by a binder thread, so we need to guard them with a mutex.
+    mutable std::mutex mLock;
 };
 
 } // namespace
@@ -449,6 +466,9 @@
     if (mPointerCaptured) {
         return mCapturedEventConverter.process(*rawEvent);
     }
+    if (mMotionAccumulator.getActiveSlotsCount() == 0) {
+        mGestureStartTime = rawEvent->when;
+    }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
         updatePalmDetectionMetrics();
@@ -514,7 +534,7 @@
     if (mDisplayId) {
         MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance();
         for (Gesture& gesture : mGesturesToProcess) {
-            out += mGestureConverter.handleGesture(when, readTime, gesture);
+            out += mGestureConverter.handleGesture(when, readTime, mGestureStartTime, gesture);
             metricsAccumulator.processGesture(mMetricsId, gesture);
         }
     }
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index ece0eca..897edca 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -113,6 +113,8 @@
     // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
     std::optional<int32_t> mDisplayId;
+
+    nsecs_t mGestureStartTime{0};
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index d06514a..b0fc903 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -30,29 +30,23 @@
                                             size_t slotCount, bool usingSlotsProtocol) {
     mUsingSlotsProtocol = usingSlotsProtocol;
     mSlots = std::vector<Slot>(slotCount);
-    reset(deviceContext);
-}
 
-void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) {
-    resetSlots();
-
-    if (!mUsingSlotsProtocol) {
-        return;
-    }
-
-    // Query the driver for the current slot index and use it as the initial slot before we
-    // start reading events from the device.  It is possible that the current slot index will
-    // not be the same as it was when the first event was written into the evdev buffer, which
-    // means the input mapper could start out of sync with the initial state of the events in
-    // the evdev buffer. In the extremely unlikely case that this happens, the data from two
-    // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the
-    // touch point to "jump", but at least there will be no stuck touches.
-    int32_t initialSlot;
-    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-        status == OK) {
-        mCurrentSlot = initialSlot;
-    } else {
-        ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
+    mCurrentSlot = -1;
+    if (mUsingSlotsProtocol) {
+        // Query the driver for the current slot index and use it as the initial slot before we
+        // start reading events from the device.  It is possible that the current slot index will
+        // not be the same as it was when the first event was written into the evdev buffer, which
+        // means the input mapper could start out of sync with the initial state of the events in
+        // the evdev buffer. In the extremely unlikely case that this happens, the data from two
+        // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the
+        // touch point to "jump", but at least there will be no stuck touches.
+        int32_t initialSlot;
+        if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
+            status == OK) {
+            mCurrentSlot = initialSlot;
+        } else {
+            ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
+        }
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 5b55e3d..0e3e2bb 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -83,7 +83,6 @@
         LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index);
         return mSlots[index];
     }
-    void reset(const InputDeviceContext& deviceContext);
 
 private:
     int32_t mCurrentSlot;
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 9552104..01e983a 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -35,8 +35,14 @@
 
 namespace {
 
+// This will disable the tap to click while the user is typing on a physical keyboard
 const bool ENABLE_TOUCHPAD_PALM_REJECTION = input_flags::enable_touchpad_typing_palm_rejection();
 
+// In addition to v1, v2 will also cancel ongoing move gestures while typing and add delay in
+// re-enabling the tap to click.
+const bool ENABLE_TOUCHPAD_PALM_REJECTION_V2 =
+        input_flags::enable_v2_touchpad_typing_palm_rejection();
+
 uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) {
     switch (gesturesButton) {
         case GESTURES_BUTTON_LEFT:
@@ -75,6 +81,7 @@
     out << StringPrintf("Button state: 0x%08x\n", mButtonState);
     out << "Down time: " << mDownTime << "\n";
     out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n";
+    out << "Enable Tap Timestamp: " << mWhenToEnableTapToClick << "\n";
     return out.str();
 }
 
@@ -109,8 +116,6 @@
 void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const {
     info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0);
 
-    // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture
-    // is enabled.
     if (!mBoundsInLogicalDisplay.isEmpty()) {
         info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, mBoundsInLogicalDisplay.left,
                             mBoundsInLogicalDisplay.right, 0, 0, 0);
@@ -131,6 +136,7 @@
 }
 
 std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      nsecs_t gestureStartTime,
                                                       const Gesture& gesture) {
     if (!mDisplayId) {
         // Ignore gestures when there is no target display configured.
@@ -139,13 +145,13 @@
 
     switch (gesture.type) {
         case kGestureTypeMove:
-            return {handleMove(when, readTime, gesture)};
+            return handleMove(when, readTime, gestureStartTime, gesture);
         case kGestureTypeButtonsChange:
             return handleButtonsChange(when, readTime, gesture);
         case kGestureTypeScroll:
             return handleScroll(when, readTime, gesture);
         case kGestureTypeFling:
-            return handleFling(when, readTime, gesture);
+            return handleFling(when, readTime, gestureStartTime, gesture);
         case kGestureTypeSwipe:
             return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
                                           gesture.details.swipe.dy);
@@ -162,18 +168,41 @@
     }
 }
 
-NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
-                                              const Gesture& gesture) {
+std::list<NotifyArgs> GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
+                                                   nsecs_t gestureStartTime,
+                                                   const Gesture& gesture) {
     float deltaX = gesture.details.move.dx;
     float deltaY = gesture.details.move.dy;
-    if (ENABLE_TOUCHPAD_PALM_REJECTION && (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) {
-        enableTapToClick();
+    if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
+        bool wasHoverCancelled = mIsHoverCancelled;
+        // Gesture will be cancelled if it started before the user started typing and
+        // there is a active IME connection.
+        mIsHoverCancelled = gestureStartTime <= mReaderContext.getLastKeyDownTimestamp() &&
+                mReaderContext.getPolicy()->isInputMethodConnectionActive();
+
+        if (!wasHoverCancelled && mIsHoverCancelled) {
+            // This is the first event of the cancelled gesture, we won't return because we need to
+            // generate a HOVER_EXIT event
+            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
+        } else if (mIsHoverCancelled) {
+            return {};
+        }
     }
+
     rotateDelta(mOrientation, &deltaX, &deltaY);
 
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->move(deltaX, deltaY);
-    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    // Update the cursor, and enable tap to click if the gesture is not cancelled
+    if (!mIsHoverCancelled) {
+        // handleFling calls hoverMove with zero delta on FLING_TAP_DOWN. Don't enable tap to click
+        // for this case as subsequent handleButtonsChange may choose to ignore this tap.
+        if ((ENABLE_TOUCHPAD_PALM_REJECTION || ENABLE_TOUCHPAD_PALM_REJECTION_V2) &&
+            (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) {
+            enableTapToClick(when);
+        }
+        mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+        mPointerController->move(deltaX, deltaY);
+        mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
 
     const auto [xCursorPosition, yCursorPosition] =
             mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
@@ -187,10 +216,12 @@
     const bool down = isPointerDown(mButtonState);
     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
 
-    const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
-    return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
-                          /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition,
-                          yCursorPosition);
+    const int32_t action = mIsHoverCancelled
+            ? AMOTION_EVENT_ACTION_HOVER_EXIT
+            : (down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE);
+    return {makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
+                           /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition,
+                           yCursorPosition)};
 }
 
 std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
@@ -210,8 +241,15 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
 
-    if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) {
-        enableTapToClick();
+    // V2 palm rejection should override V1
+    if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
+        enableTapToClick(when);
+        if (gesture.details.buttons.is_tap && when <= mWhenToEnableTapToClick) {
+            // return early to prevent this tap
+            return out;
+        }
+    } else if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) {
+        enableTapToClick(when);
         if (gesture.details.buttons.is_tap) {
             // return early to prevent this tap
             return out;
@@ -348,6 +386,7 @@
 }
 
 std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTime,
+                                                    nsecs_t gestureStartTime,
                                                     const Gesture& gesture) {
     switch (gesture.details.fling.fling_state) {
         case GESTURES_FLING_START:
@@ -366,13 +405,10 @@
                 // magnitude, which will also result in the pointer icon being updated.
                 // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been
                 //  initiated with a touchpad.
-                if (!mReaderContext.isPreventingTouchpadTaps()) {
-                    enableTapToClick();
-                }
-                return {handleMove(when, readTime,
-                                   Gesture(kGestureMove, gesture.start_time, gesture.end_time,
-                                           /*dx=*/0.f,
-                                           /*dy=*/0.f))};
+                return handleMove(when, readTime, gestureStartTime,
+                                  Gesture(kGestureMove, gesture.start_time, gesture.end_time,
+                                          /*dx=*/0.f,
+                                          /*dy=*/0.f));
             }
             break;
         default:
@@ -598,8 +634,11 @@
             /* videoFrames= */ {}};
 }
 
-void GestureConverter::enableTapToClick() {
-    mReaderContext.setPreventingTouchpadTaps(false);
+void GestureConverter::enableTapToClick(nsecs_t when) {
+    if (mReaderContext.isPreventingTouchpadTaps()) {
+        mWhenToEnableTapToClick = when + TAP_ENABLE_DELAY_NANOS.count();
+        mReaderContext.setPreventingTouchpadTaps(false);
+    }
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 732a4b2..88e7b99 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -34,6 +34,13 @@
 
 namespace android {
 
+using std::chrono_literals::operator""ms;
+/**
+ * This duration is decided based on internal team testing, it may be updated after testing with
+ * larger groups
+ */
+constexpr std::chrono::nanoseconds TAP_ENABLE_DELAY_NANOS = 400ms;
+
 // Converts Gesture structs from the gestures library into NotifyArgs and the appropriate
 // PointerController calls.
 class GestureConverter {
@@ -53,17 +60,20 @@
     void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      nsecs_t gestureStartTime,
                                                       const Gesture& gesture);
 
 private:
-    [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime,
-                                              const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> handleMove(nsecs_t when, nsecs_t readTime,
+                                                   nsecs_t gestureStartTime,
+                                                   const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
                                                             const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> releaseAllButtons(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
                                                      const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime,
+                                                    nsecs_t gestureStartTime,
                                                     const Gesture& gesture);
     [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime);
 
@@ -82,7 +92,9 @@
                                     const PointerCoords* pointerCoords, float xCursorPosition,
                                     float yCursorPosition);
 
-    void enableTapToClick();
+    void enableTapToClick(nsecs_t when);
+    bool mIsHoverCancelled{false};
+    nsecs_t mWhenToEnableTapToClick{0};
 
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index e9d93af..fb2db06 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -63,7 +63,7 @@
 
     void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
 
-    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
+    void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {}
 
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
         return 0;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index d2b68dd..69772af 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -41,10 +41,14 @@
 
 const auto TOUCHPAD_PALM_REJECTION =
         ACONFIG_FLAG(input_flags, enable_touchpad_typing_palm_rejection);
+const auto TOUCHPAD_PALM_REJECTION_V2 =
+        ACONFIG_FLAG(input_flags, enable_v2_touchpad_typing_palm_rejection);
 
 } // namespace
 
 using testing::AllOf;
+using testing::ElementsAre;
+using testing::VariantWith;
 
 class GestureConverterTestBase : public testing::Test {
 protected:
@@ -107,14 +111,14 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+                              WithToolType(ToolType::FINGER), WithButtonState(0),
+                              WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 }
@@ -126,14 +130,14 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
+                              WithToolType(ToolType::FINGER), WithButtonState(0),
+                              WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
 }
@@ -147,67 +151,69 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
     ASSERT_EQ(3u, args.size());
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Then release the left button
     Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                           /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                              WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                              WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                              WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Finally release the right button
     Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
                            /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, DragWithButton) {
@@ -219,32 +225,33 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
                         /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Move
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+                              WithToolType(ToolType::FINGER),
+                              WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 
@@ -252,24 +259,27 @@
     Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                       /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                       /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0),
+                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0),
+                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0),
+                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll) {
@@ -279,50 +289,52 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y - 10),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(POINTER_X, POINTER_Y - 10),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y - 15),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(POINTER_X, POINTER_Y - 15),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X, POINTER_Y - 15),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                              WithCoords(POINTER_X, POINTER_Y - 15),
+                              WithGestureScrollDistance(0, 0, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
@@ -333,43 +345,47 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 10, POINTER_Y),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(POINTER_X - 10, POINTER_Y),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 15, POINTER_Y),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(POINTER_X - 15, POINTER_Y),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X - 15, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                              WithCoords(POINTER_X - 15, POINTER_Y),
+                              WithGestureScrollDistance(0, 0, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) {
@@ -378,21 +394,22 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionClassification(MotionClassification::NONE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::NONE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) {
@@ -401,20 +418,21 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
 }
@@ -426,17 +444,19 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
                         /*dy=*/10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
     ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
@@ -446,16 +466,17 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
                          /*dy=*/5);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
@@ -472,7 +493,8 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
@@ -523,7 +545,7 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -540,30 +562,36 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
@@ -574,7 +602,8 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
@@ -616,7 +645,7 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -631,23 +660,24 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
@@ -657,7 +687,8 @@
 
     Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                          /* dx= */ 10, /* dy= */ 0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(5u, args.size());
 
     // Four fake fingers should be created. We don't actually care where they are, so long as they
@@ -720,7 +751,7 @@
 
     Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 5, /* dy= */ 0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -739,38 +770,46 @@
     EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Inwards) {
@@ -780,51 +819,57 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(POINTER_X - 100, POINTER_Y),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, POINTER_X + 100, POINTER_Y),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(0.8f, EPSILON),
-                      WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
-                      WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(0.8f, EPSILON),
+                              WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
+                              WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Outwards) {
@@ -834,51 +879,57 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(POINTER_X - 100, POINTER_Y),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, POINTER_X + 100, POINTER_Y),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.2f, EPSILON),
-                      WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
-                      WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(1.2f, EPSILON),
+                              WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
+                              WithPointerCoords(1, POINTER_X + 120, POINTER_Y),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) {
@@ -888,21 +939,22 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) {
@@ -912,21 +964,22 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like scroll.
     Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
                           /*dy=*/0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
 }
@@ -939,28 +992,28 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringScroll) {
@@ -969,18 +1022,18 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X, POINTER_Y - 10),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                              WithCoords(POINTER_X, POINTER_Y - 10),
+                              WithGestureScrollDistance(0, 0, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
@@ -990,31 +1043,35 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringPinch) {
@@ -1024,22 +1081,24 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, FlingTapDown) {
@@ -1049,7 +1108,8 @@
 
     Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
 
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
@@ -1069,52 +1129,57 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+                              WithToolType(ToolType::FINGER), WithButtonState(0),
+                              WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture);
 
-    ASSERT_EQ(5u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithButtonState(0), WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Click) {
@@ -1125,62 +1190,71 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+                              WithToolType(ToolType::FINGER), WithButtonState(0),
+                              WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
 
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
-
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithButtonState(0), WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION),
+                  REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
     // Tap should be ignored when disabled
     mReader->getContext()->setPreventingTouchpadTaps(true);
 
@@ -1188,9 +1262,45 @@
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+                              WithToolType(ToolType::FINGER), WithButtonState(0),
+                              WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
+    // Tap should be ignored when disabled
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
 
     ASSERT_EQ(1u, args.size());
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
@@ -1198,18 +1308,73 @@
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
                       WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
                       WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
 
-    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
 
     // no events should be generated
     ASSERT_EQ(0u, args.size());
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+
+    // taps before the threshold should still be ignored
+    currentTime += TAP_ENABLE_DELAY_NANOS.count();
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // taps after the threshold should be recognised
+    currentTime += 1;
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    ASSERT_EQ(5u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(1),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(0)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0),
+                      WithButtonState(0)));
 }
 
 TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled,
@@ -1223,58 +1388,64 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+                              WithToolType(ToolType::FINGER), WithButtonState(0),
+                              WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
 
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithButtonState(0), WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
@@ -1290,14 +1461,14 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+                              WithToolType(ToolType::FINGER), WithButtonState(0),
+                              WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 
@@ -1305,6 +1476,45 @@
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
+TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    const nsecs_t gestureStartTime = 1000;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Start a move gesture at gestureStartTime
+    Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+
+    // Key presses with IME connection should cancel ongoing move gesture
+    nsecs_t currentTime = gestureStartTime + 100;
+    mFakePolicy->setIsInputMethodConnectionActive(true);
+    mReader->getContext()->setLastKeyDownTimestamp(currentTime);
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))));
+
+    // any updates in existing move gesture should be ignored
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_EQ(0u, args.size());
+
+    // New gesture should not be affected
+    currentTime += 100;
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+}
+
 // TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
 //   logic can be removed.
 class GestureConverterTestWithChoreographer : public GestureConverterTestBase {
@@ -1321,13 +1531,14 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) {
@@ -1337,13 +1548,14 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(10, 5), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(10, 5), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) {
@@ -1355,65 +1567,64 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Then release the left button
     Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                           /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                              WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                              WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Finally release the right button
     Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
                            /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, DragWithButton) {
@@ -1425,53 +1636,53 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
                         /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Move
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Release the button
     Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                       /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                       /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Scroll) {
@@ -1481,47 +1692,50 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -10),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(0, -10),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0 - 15),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0 - 15),
+                              WithGestureScrollDistance(0, 0, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) {
@@ -1532,40 +1746,46 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-10, 0),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(-10, 0),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(-15, 0),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(-15, 0),
+                              WithGestureScrollDistance(0, 0, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) {
@@ -1574,21 +1794,22 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionClassification(MotionClassification::NONE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::NONE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) {
@@ -1597,20 +1818,21 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
 }
@@ -1622,17 +1844,18 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
                         /*dy=*/10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
@@ -1642,16 +1865,17 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
                          /*dy=*/5);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
@@ -1668,7 +1892,8 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
@@ -1719,7 +1944,7 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -1736,30 +1961,36 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) {
@@ -1770,7 +2001,8 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
@@ -1812,7 +2044,7 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -1827,23 +2059,24 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) {
@@ -1853,7 +2086,8 @@
 
     Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                          /* dx= */ 10, /* dy= */ 0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(5u, args.size());
 
     // Four fake fingers should be created. We don't actually care where they are, so long as they
@@ -1916,7 +2150,7 @@
 
     Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 5, /* dy= */ 0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -1935,38 +2169,46 @@
     EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) {
@@ -1976,50 +2218,56 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(-100, 0), WithPointerCount(1u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, -80, 0),
-                      WithPointerCoords(1, 80, 0), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(0.8f, EPSILON),
+                              WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) {
@@ -2029,50 +2277,56 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(-100, 0), WithPointerCount(1u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.1f, EPSILON), WithPointerCoords(0, -110, 0),
-                      WithPointerCoords(1, 110, 0), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(1.1f, EPSILON),
+                              WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) {
@@ -2082,21 +2336,22 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) {
@@ -2106,21 +2361,22 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like scroll.
     Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
                           /*dy=*/0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
 }
@@ -2133,27 +2389,27 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) {
@@ -2162,17 +2418,17 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, -10),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, -10),
+                              WithGestureScrollDistance(0, 0, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) {
@@ -2182,31 +2438,35 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) {
@@ -2216,22 +2476,24 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) {
@@ -2241,7 +2503,8 @@
 
     Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
 
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
@@ -2257,50 +2520,54 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture);
 
-    ASSERT_EQ(5u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Click) {
@@ -2311,7 +2578,8 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     ASSERT_EQ(1u, args.size());
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
@@ -2322,49 +2590,56 @@
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
 
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
 
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION),
+                  REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
     // Tap should be ignored when disabled
     mReader->getContext()->setPreventingTouchpadTaps(true);
 
@@ -2372,21 +2647,22 @@
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
-    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
 
     // no events should be generated
     ASSERT_EQ(0u, args.size());
@@ -2395,6 +2671,96 @@
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabledWithDelay,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
+    // Tap should be ignored when disabled
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+
+    // taps before the threshold should still be ignored
+    currentTime += TAP_ENABLE_DELAY_NANOS.count();
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // taps after the threshold should be recognised
+    currentTime += 1;
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    ASSERT_EQ(5u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(1),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(0)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0),
+                      WithButtonState(0)));
+}
+
 TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled,
                   REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
     // Click should still produce button press/release events
@@ -2406,56 +2772,62 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
-    ASSERT_EQ(2u, args.size());
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
 
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
-                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
@@ -2471,16 +2843,57 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, KeypressCancelsHoverMove,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    const nsecs_t gestureStartTime = 1000;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Start a move gesture at gestureStartTime
+    Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+
+    // Key presses with IME connection should cancel ongoing move gesture
+    nsecs_t currentTime = gestureStartTime + 100;
+    mFakePolicy->setIsInputMethodConnectionActive(true);
+    mReader->getContext()->setLastKeyDownTimestamp(currentTime);
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))));
+
+    // any updates in existing move gesture should be ignored
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_EQ(0u, args.size());
+
+    // New gesture should not be affected
+    currentTime += 100;
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 5002391..a880a4c 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -549,7 +549,7 @@
         }
     }
 
-    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
+    void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {}
 
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
         nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
@@ -572,7 +572,8 @@
         /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
          * essentially a passthrough for notifySwitch.
          */
-        mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask);
+        mLastNotifySwitch =
+                NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask);
     }
 
     void pokeUserActivity(nsecs_t, int32_t, int32_t) override {
@@ -836,7 +837,8 @@
 }
 
 TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
-    NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1,
+    NotifySwitchArgs args(InputEvent::nextId(), /*eventTime=*/20, /*policyFlags=*/0,
+                          /*switchValues=*/1,
                           /*switchMask=*/2);
     mDispatcher->notifySwitch(args);
 
@@ -1182,6 +1184,11 @@
         mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
     }
 
+    void setGlobalStylusBlocksTouch(bool shouldGlobalStylusBlockTouch) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH,
+                             shouldGlobalStylusBlockTouch);
+    }
+
     void setAlpha(float alpha) { mInfo.alpha = alpha; }
 
     void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
@@ -1221,31 +1228,27 @@
 
     void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
 
-    KeyEvent* consumeKey(bool handled = true) {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consume failed : no event";
-            return nullptr;
+    const KeyEvent& consumeKey(bool handled = true) {
+        const InputEvent& event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
+        if (event.getType() != InputEventType::KEY) {
+            LOG(FATAL) << "Instead of key event, got " << event;
         }
-        if (event->getType() != InputEventType::KEY) {
-            ADD_FAILURE() << "Instead of key event, got " << *event;
-            return nullptr;
-        }
-        return static_cast<KeyEvent*>(event);
+        return static_cast<const KeyEvent&>(event);
     }
 
     void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
-        KeyEvent* keyEvent = consumeKey();
-        ASSERT_NE(nullptr, keyEvent) << "Did not get a key event, but expected " << matcher;
-        ASSERT_THAT(*keyEvent, matcher);
+        const KeyEvent& keyEvent = consumeKey();
+        ASSERT_THAT(keyEvent, matcher);
     }
 
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags);
+        consumeKeyEvent(AllOf(WithKeyAction(ACTION_DOWN), WithDisplayId(expectedDisplayId),
+                              WithFlags(expectedFlags)));
     }
 
     void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags);
+        consumeKeyEvent(AllOf(WithKeyAction(ACTION_UP), WithDisplayId(expectedDisplayId),
+                              WithFlags(expectedFlags)));
     }
 
     void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
@@ -1267,44 +1270,46 @@
 
     void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt,
                               std::optional<int32_t> expectedFlags = std::nullopt) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
-                     expectedFlags);
+        consumeMotionEvent(
+                AllOf(WithMotionAction(ACTION_DOWN),
+                      testing::Conditional(expectedDisplayId.has_value(),
+                                           WithDisplayId(*expectedDisplayId), testing::_),
+                      testing::Conditional(expectedFlags.has_value(), WithFlags(*expectedFlags),
+                                           testing::_)));
     }
 
     void consumeMotionPointerDown(int32_t pointerIdx,
                                   int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                                   int32_t expectedFlags = 0) {
-        int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId),
+                                 WithFlags(expectedFlags)));
     }
 
     void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                                 int32_t expectedFlags = 0) {
-        int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId),
+                                 WithFlags(expectedFlags)));
     }
 
     void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                          int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
-                     expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDisplayId(expectedDisplayId),
+                                 WithFlags(expectedFlags)));
     }
 
     void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                               int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId,
-                     expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE),
+                                 WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
     }
 
-    void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                                              int32_t expectedFlags = 0) {
-        MotionEvent* motionEvent = consumeMotion();
-        ASSERT_NE(nullptr, motionEvent);
-        EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent->getActionMasked());
-        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getX());
-        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getY());
+    void consumeMotionOutsideWithZeroedCoords() {
+        consumeMotionEvent(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), WithRawCoords(0, 0)));
     }
 
     void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
@@ -1319,21 +1324,15 @@
         mInputReceiver->consumeCaptureEvent(hasCapture);
     }
 
-    const MotionEvent& consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
-        MotionEvent* motionEvent = consumeMotion();
-        if (nullptr == motionEvent) {
-            LOG(FATAL) << "Did not get a motion event, but expected " << matcher;
+    const MotionEvent& consumeMotionEvent(
+            const ::testing::Matcher<MotionEvent>& matcher = testing::_) {
+        const InputEvent& event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+        if (event.getType() != InputEventType::MOTION) {
+            LOG(FATAL) << "Instead of motion event, got " << event;
         }
-        EXPECT_THAT(*motionEvent, matcher);
-        return *motionEvent;
-    }
-
-    void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
-                      std::optional<int32_t> expectedDisplayId,
-                      std::optional<int32_t> expectedFlags) {
-        ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
-        mInputReceiver->consumeEvent(expectedEventType, expectedAction, expectedDisplayId,
-                                     expectedFlags);
+        const auto& motionEvent = static_cast<const MotionEvent&>(event);
+        EXPECT_THAT(motionEvent, matcher);
+        return motionEvent;
     }
 
     void consumeDragEvent(bool isExiting, float x, float y) {
@@ -1364,26 +1363,6 @@
         mInputReceiver->sendTimeline(inputEventId, timeline);
     }
 
-    InputEvent* consume(std::chrono::milliseconds timeout, bool handled = true) {
-        if (mInputReceiver == nullptr) {
-            return nullptr;
-        }
-        return mInputReceiver->consume(timeout, handled);
-    }
-
-    MotionEvent* consumeMotion() {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consume failed : no event";
-            return nullptr;
-        }
-        if (event->getType() != InputEventType::MOTION) {
-            ADD_FAILURE() << "Instead of motion event, got " << *event;
-            return nullptr;
-        }
-        return static_cast<MotionEvent*>(event);
-    }
-
     void assertNoEvents() {
         if (mInputReceiver == nullptr &&
             mInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
@@ -1415,6 +1394,17 @@
     std::shared_ptr<FakeInputReceiver> mInputReceiver;
     static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
     friend class sp<FakeWindowHandle>;
+
+    const InputEvent& consume(std::chrono::milliseconds timeout, bool handled = true) {
+        if (mInputReceiver == nullptr) {
+            LOG(FATAL) << "Cannot consume event from a window with no input event receiver";
+        }
+        InputEvent* event = mInputReceiver->consume(timeout, handled);
+        if (event == nullptr) {
+            LOG(FATAL) << "Consume failed: no event";
+        }
+        return *event;
+    }
 };
 
 std::atomic<int32_t> FakeWindowHandle::sId{1};
@@ -1579,9 +1569,9 @@
 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, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, POLICY_FLAG_PASS_TO_USER, action, /*flags=*/0, AKEYCODE_A, KEY_A,
-                       AMETA_NONE, currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, POLICY_FLAG_PASS_TO_USER, action,
+                       /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, currentTime);
 
     return args;
 }
@@ -1590,9 +1580,9 @@
                                                 int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C, AMETA_META_ON,
-                       currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C,
+                       AMETA_META_ON, currentTime);
 
     return args;
 }
@@ -1601,9 +1591,9 @@
                                               int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST, KEY_ASSISTANT,
-                       AMETA_NONE, currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST,
+                       KEY_ASSISTANT, AMETA_NONE, currentTime);
 
     return args;
 }
@@ -1631,9 +1621,9 @@
 
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid motion event.
-    NotifyMotionArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, source, displayId,
-                          POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, /*flags=*/0,
-                          AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
+    NotifyMotionArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, source,
+                          displayId, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0,
+                          /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
                           pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
@@ -1652,7 +1642,8 @@
 
 static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(
         const PointerCaptureRequest& request) {
-    return NotifyPointerCaptureChangedArgs(/*id=*/0, systemTime(SYSTEM_TIME_MONOTONIC), request);
+    return NotifyPointerCaptureChangedArgs(InputEvent::nextId(), systemTime(SYSTEM_TIME_MONOTONIC),
+                                           request);
 }
 
 } // namespace
@@ -3355,6 +3346,150 @@
 }
 
 /**
+ * If stylus is down anywhere on the screen, then touches should not be delivered to windows that
+ * have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
+ *
+ * Two windows: one on the left and one on the right.
+ * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config.
+ * Stylus down on the left window, and then touch down on the right window.
+ * Check that the right window doesn't get touches while the stylus is down on the left window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusDownBlocksTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> sbtRightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher,
+                                       "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT);
+    sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
+    sbtRightWindow->setGlobalStylusBlocksTouch(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+
+    // Stylus down in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Finger tap on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+
+    // The touch should be blocked, because stylus is down somewhere else on screen!
+    sbtRightWindow->assertNoEvents();
+
+    // Continue stylus motion, and ensure it's not impacted.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDeviceId(stylusDeviceId)));
+
+    // Now that the stylus gesture is done, touches should be getting delivered correctly.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    sbtRightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
+ * If stylus is hovering anywhere on the screen, then touches should not be delivered to windows
+ * that have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
+ *
+ * Two windows: one on the left and one on the right.
+ * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config.
+ * Stylus hover on the left window, and then touch down on the right window.
+ * Check that the right window doesn't get touches while the stylus is hovering on the left window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusHoverBlocksTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> sbtRightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher,
+                                       "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT);
+    sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
+    sbtRightWindow->setGlobalStylusBlocksTouch(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+
+    // Stylus hover in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Finger tap on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+
+    // The touch should be blocked, because stylus is hovering somewhere else on screen!
+    sbtRightWindow->assertNoEvents();
+
+    // Continue stylus motion, and ensure it's not impacted.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+
+    // Now that the stylus gesture is done, touches should be getting delivered correctly.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    sbtRightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * A spy window above a window with no input channel.
  * Start hovering with a stylus device, and then tap with it.
  * Ensure spy window receives the entire sequence.
@@ -3625,20 +3760,18 @@
 
     mDispatcher->waitForIdle();
 
-    MotionEvent* motionEvent1 = window1->consumeMotion();
-    ASSERT_NE(motionEvent1, nullptr);
+    const MotionEvent& motionEvent1 = window1->consumeMotionEvent();
     window2->assertNoEvents();
-    nsecs_t downTimeForWindow1 = motionEvent1->getDownTime();
-    ASSERT_EQ(motionEvent1->getDownTime(), motionEvent1->getEventTime());
+    nsecs_t downTimeForWindow1 = motionEvent1.getDownTime();
+    ASSERT_EQ(motionEvent1.getDownTime(), motionEvent1.getEventTime());
 
     // Now touch down on the window with another pointer
     mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}));
     mDispatcher->waitForIdle();
-    MotionEvent* motionEvent2 = window2->consumeMotion();
-    ASSERT_NE(motionEvent2, nullptr);
-    nsecs_t downTimeForWindow2 = motionEvent2->getDownTime();
+    const MotionEvent& motionEvent2 = window2->consumeMotionEvent();
+    nsecs_t downTimeForWindow2 = motionEvent2.getDownTime();
     ASSERT_NE(downTimeForWindow1, downTimeForWindow2);
-    ASSERT_EQ(motionEvent2->getDownTime(), motionEvent2->getEventTime());
+    ASSERT_EQ(motionEvent2.getDownTime(), motionEvent2.getEventTime());
 
     // Now move the pointer on the second window
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}));
@@ -4233,8 +4366,7 @@
     // When device reset happens, that key stream should be terminated with FLAG_CANCELED
     // on the app side.
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
-    window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
-                         AKEY_EVENT_FLAG_CANCELED);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED);
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) {
@@ -4473,12 +4605,12 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    const MotionEvent* event = window->consumeMotion();
-    EXPECT_EQ(POINTER_1_DOWN, event->getAction());
-    EXPECT_EQ(70, event->getX(0));  // 50 + 20
-    EXPECT_EQ(90, event->getY(0));  // 50 + 40
-    EXPECT_EQ(-10, event->getX(1)); // -30 + 20
-    EXPECT_EQ(-10, event->getY(1)); // -50 + 40
+    const MotionEvent& event = window->consumeMotionEvent();
+    EXPECT_EQ(POINTER_1_DOWN, event.getAction());
+    EXPECT_EQ(70, event.getX(0));  // 50 + 20
+    EXPECT_EQ(90, event.getY(0));  // 50 + 40
+    EXPECT_EQ(-10, event.getX(1)); // -30 + 20
+    EXPECT_EQ(-10, event.getY(1)); // -50 + 40
 }
 
 /**
@@ -4744,15 +4876,15 @@
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindowDefaultDisplay->getToken()));
 
     // windowDefaultDisplay gets cancel
-    MotionEvent* event = windowDefaultDisplay->consumeMotion();
-    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction());
+    const MotionEvent& event = windowDefaultDisplay->consumeMotionEvent();
+    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event.getAction());
 
     // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the
     // coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y
     // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of
     // SECOND_DISPLAY_ID, the x and y coordinates are 200
-    EXPECT_EQ(100, event->getX(0));
-    EXPECT_EQ(100, event->getY(0));
+    EXPECT_EQ(100, event.getX(0));
+    EXPECT_EQ(100, event.getY(0));
 }
 
 /**
@@ -4882,19 +5014,18 @@
                                                  {PointF{150, 220}}));
 
     firstWindow->assertNoEvents();
-    const MotionEvent* event = secondWindow->consumeMotion();
-    ASSERT_NE(nullptr, event);
-    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction());
+    const MotionEvent& event = secondWindow->consumeMotionEvent();
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event.getAction());
 
     // Ensure that the events from the "getRaw" API are in logical display coordinates.
-    EXPECT_EQ(300, event->getRawX(0));
-    EXPECT_EQ(880, event->getRawY(0));
+    EXPECT_EQ(300, event.getRawX(0));
+    EXPECT_EQ(880, event.getRawY(0));
 
     // Ensure that the x and y values are in the window's coordinate space.
     // The left-top of the second window is at (100, 200) in display space, which is (200, 800) in
     // the logical display space. This will be the origin of the window space.
-    EXPECT_EQ(100, event->getX(0));
-    EXPECT_EQ(80, event->getY(0));
+    EXPECT_EQ(100, event.getX(0));
+    EXPECT_EQ(80, event.getY(0));
 }
 
 TEST_F(InputDispatcherDisplayProjectionTest, CancelMotionWithCorrectCoordinates) {
@@ -5968,8 +6099,7 @@
                                              motionArgs.pointerCoords[0].getX() - 10);
 
     mDispatcher->notifyMotion(motionArgs);
-    window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
-                         /*expectedFlags=*/0);
+    window->consumeMotionMove(ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0);
 }
 
 /**
@@ -6039,10 +6169,9 @@
     const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
     mDispatcher->notifyKey(keyArgs);
 
-    KeyEvent* event = window->consumeKey();
-    ASSERT_NE(event, nullptr);
+    const KeyEvent& event = window->consumeKey();
 
-    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
+    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(event);
     ASSERT_NE(verified, nullptr);
     ASSERT_EQ(verified->type, VerifiedInputEvent::Type::KEY);
 
@@ -6083,10 +6212,9 @@
                                ADISPLAY_ID_DEFAULT);
     mDispatcher->notifyMotion(motionArgs);
 
-    MotionEvent* event = window->consumeMotion();
-    ASSERT_NE(event, nullptr);
+    const MotionEvent& event = window->consumeMotionEvent();
 
-    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
+    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(event);
     ASSERT_NE(verified, nullptr);
     ASSERT_EQ(verified->type, VerifiedInputEvent::Type::MOTION);
 
@@ -6664,9 +6792,8 @@
     }
 
     void consumeKey(bool handled, const ::testing::Matcher<KeyEvent>& matcher) {
-        KeyEvent* event = mWindow->consumeKey(handled);
-        ASSERT_NE(event, nullptr) << "Did not receive key event";
-        ASSERT_THAT(*event, matcher);
+        const KeyEvent& event = mWindow->consumeKey(handled);
+        ASSERT_THAT(event, matcher);
     }
 };
 
@@ -6897,7 +7024,7 @@
         mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
-        mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
+        mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
                               /*expectedFlags=*/0);
     }
 };
@@ -6967,10 +7094,9 @@
     GTEST_SKIP() << "Flaky test (b/270393106)";
     sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        KeyEvent* repeatEvent = mWindow->consumeKey();
-        ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
+        const KeyEvent& repeatEvent = mWindow->consumeKey();
         EXPECT_EQ(IdGenerator::Source::INPUT_DISPATCHER,
-                  IdGenerator::getSource(repeatEvent->getId()));
+                  IdGenerator::getSource(repeatEvent.getId()));
     }
 }
 
@@ -6980,9 +7106,8 @@
 
     std::unordered_set<int32_t> idSet;
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        KeyEvent* repeatEvent = mWindow->consumeKey();
-        ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
-        int32_t id = repeatEvent->getId();
+        const KeyEvent& repeatEvent = mWindow->consumeKey();
+        int32_t id = repeatEvent.getId();
         EXPECT_EQ(idSet.end(), idSet.find(id));
         idSet.insert(id);
     }
@@ -7071,8 +7196,7 @@
     mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
 
     // Old focus should receive a cancel event.
-    windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE,
-                                    AKEY_EVENT_FLAG_CANCELED);
+    windowInSecondary->consumeKeyUp(ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED);
 
     // Test inject a key down, should timeout because of no target window.
     ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
@@ -7550,24 +7674,21 @@
     void consumeMotionEvent(const sp<FakeWindowHandle>& window, int32_t expectedAction,
                             const std::vector<PointF>& points) {
         const std::string name = window->getName();
-        MotionEvent* motionEvent = window->consumeMotion();
+        const MotionEvent& motionEvent =
+                window->consumeMotionEvent(WithMotionAction(expectedAction));
 
-        ASSERT_NE(nullptr, motionEvent)
-                << name.c_str() << ": consumer should have returned non-NULL event.";
-
-        ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction));
-        ASSERT_EQ(points.size(), motionEvent->getPointerCount());
+        ASSERT_EQ(points.size(), motionEvent.getPointerCount());
 
         for (size_t i = 0; i < points.size(); i++) {
             float expectedX = points[i].x;
             float expectedY = points[i].y;
 
-            EXPECT_EQ(expectedX, motionEvent->getX(i))
+            EXPECT_EQ(expectedX, motionEvent.getX(i))
                     << "expected " << expectedX << " for x[" << i << "] coord of " << name.c_str()
-                    << ", got " << motionEvent->getX(i);
-            EXPECT_EQ(expectedY, motionEvent->getY(i))
+                    << ", got " << motionEvent.getX(i);
+            EXPECT_EQ(expectedY, motionEvent.getY(i))
                     << "expected " << expectedY << " for y[" << i << "] coord of " << name.c_str()
-                    << ", got " << motionEvent->getY(i);
+                    << ", got " << motionEvent.getY(i);
         }
     }
 
@@ -8380,8 +8501,7 @@
                                         .build()));
     mFocusedWindow->consumeMotionDown();
     mFocusedWindow->consumeMotionUp();
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -8457,8 +8577,7 @@
 // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events.
 TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
     tapOnFocusedWindow();
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // Receive the events, but don't respond
     std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(downEventSequenceNum);
@@ -8590,8 +8709,7 @@
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                                  {FOCUSED_WINDOW_LOCATION}));
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
 
     // Touch Window 2
     mDispatcher->notifyMotion(
@@ -9723,7 +9841,7 @@
 
     // Receives cancel for first pointer after next pointer down
     mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
-    mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    mSpyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithPointerIds({1})));
     mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
 
     mSpyWindow->assertNoEvents();
@@ -9733,13 +9851,13 @@
     mDragWindow->assertNoEvents();
 
     const MotionEvent firstFingerMoveEvent =
-            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+            MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(60).y(60))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+              injectMotionEvent(*mDispatcher, firstFingerMoveEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
@@ -9930,8 +10048,7 @@
                                         .displayId(SECOND_DISPLAY_ID)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
-                                    SECOND_DISPLAY_ID, /*expectedFlag=*/0);
+    windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID, /*expectedFlag=*/0);
     // Update window again.
     mDispatcher->onWindowInfosChanged(
             {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index e0a3e94..c6536de 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2894,9 +2894,9 @@
     mapper.assertConfigureWasCalled();
     mapper.assertResetWasNotCalled();
 
-    RawEvent event{.deviceId = EVENTHUB_ID,
+    RawEvent event{.when = ARBITRARY_TIME,
                    .readTime = ARBITRARY_TIME,
-                   .when = ARBITRARY_TIME,
+                   .deviceId = EVENTHUB_ID,
                    .type = EV_SYN,
                    .code = SYN_REPORT,
                    .value = 0};
@@ -10971,7 +10971,7 @@
     ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
 }
 
-TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) {
+TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
@@ -10994,36 +10994,25 @@
     ASSERT_NO_FATAL_FAILURE(
             mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN)));
 
-    // Reset the mapper. When the mapper is reset, the touch state is also cleared.
+    // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
+    // preserved. Resetting should cancel the ongoing gesture.
     resetMapper(mapper, ARBITRARY_TIME);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
 
-    // Move the second slot pointer, and ensure there are no events, because the touch state was
-    // cleared and no slots should be in use.
+    // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
+    // the existing touch state to generate a down event.
     processPosition(mapper, 301, 302);
     processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-    // Release both fingers.
-    processId(mapper, INVALID_TRACKING_ID);
-    processSlot(mapper, FIRST_SLOT);
-    processId(mapper, INVALID_TRACKING_ID);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-    // Start a new gesture, and ensure we get a DOWN event for it.
-    processId(mapper, FIRST_TRACKING_ID);
-    processPosition(mapper, 200, 300);
-    processPressure(mapper, RAW_PRESSURE_MAX);
-    processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f))));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
-TEST_F(MultiTouchInputMapperTest, ResetClearsTouchStateWithNoPointersDown) {
+TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
@@ -11151,66 +11140,6 @@
     ASSERT_FALSE(fakePointerController->isPointerShown());
 }
 
-TEST_F(MultiTouchInputMapperTest, SimulateKernelBufferOverflow) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT | PRESSURE);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    // First finger down.
-    processId(mapper, FIRST_TRACKING_ID);
-    processPosition(mapper, 100, 200);
-    processPressure(mapper, RAW_PRESSURE_MAX);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
-
-    // Assume the kernel buffer overflows, and we get a SYN_DROPPED event.
-    // This will reset the mapper, and thus also reset the touch state.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_DROPPED, 0);
-    resetMapper(mapper, ARBITRARY_TIME);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
-
-    // Since the touch state was reset, it doesn't know which slots are active, so any movements
-    // are ignored.
-    processPosition(mapper, 101, 201);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-    // Second finger goes down. This is the first active finger, so we get a DOWN event.
-    processSlot(mapper, SECOND_SLOT);
-    processId(mapper, SECOND_TRACKING_ID);
-    processPosition(mapper, 400, 500);
-    processPressure(mapper, RAW_PRESSURE_MAX);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
-
-    // First slot is still ignored, only the second one is active.
-    processSlot(mapper, FIRST_SLOT);
-    processPosition(mapper, 102, 202);
-    processSlot(mapper, SECOND_SLOT);
-    processPosition(mapper, 401, 501);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
-
-    // Both slots up, so we get the UP event for the active pointer.
-    processSlot(mapper, FIRST_SLOT);
-    processId(mapper, INVALID_TRACKING_ID);
-    processSlot(mapper, SECOND_SLOT);
-    processId(mapper, INVALID_TRACKING_ID);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(
-            mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-}
-
 // --- MultiTouchInputMapperTest_ExternalDevice ---
 
 class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 66fdaa4..8ba497a 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -464,6 +464,53 @@
     return WithPointersMatcher(pointers);
 }
 
+/// Pointer ids matcher
+class WithPointerIdsMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithPointerIdsMatcher(std::set<int32_t> pointerIds) : mPointerIds(pointerIds) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+        std::set<int32_t> actualPointerIds;
+        for (size_t pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) {
+            const PointerProperties* properties = event.getPointerProperties(pointerIndex);
+            actualPointerIds.insert(properties->id);
+        }
+
+        if (mPointerIds != actualPointerIds) {
+            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
+                << dumpSet(actualPointerIds);
+            return false;
+        }
+        return true;
+    }
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        std::set<int32_t> actualPointerIds;
+        for (const PointerProperties& properties : event.pointerProperties) {
+            actualPointerIds.insert(properties.id);
+        }
+
+        if (mPointerIds != actualPointerIds) {
+            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
+                << dumpSet(actualPointerIds);
+            return false;
+        }
+        return true;
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with pointer ids " << dumpSet(mPointerIds); }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointer ids"; }
+
+private:
+    const std::set<int32_t> mPointerIds;
+};
+
+inline WithPointerIdsMatcher WithPointerIds(const std::set<int32_t /*id*/>& pointerIds) {
+    return WithPointerIdsMatcher(pointerIds);
+}
+
 /// Key code
 class WithKeyCodeMatcher {
 public:
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
index ef723c2..bd2b10a 100644
--- a/services/powermanager/WorkDuration.cpp
+++ b/services/powermanager/WorkDuration.cpp
@@ -25,8 +25,9 @@
 
 WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
                            int64_t cpuDurationNanos, int64_t gpuDurationNanos)
-      : workPeriodStartTimestampNanos(startTimestampNanos),
+      : timestampNanos(0),
         actualTotalDurationNanos(totalDurationNanos),
+        workPeriodStartTimestampNanos(startTimestampNanos),
         actualCpuDurationNanos(cpuDurationNanos),
         actualGpuDurationNanos(gpuDurationNanos) {}
 
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
index 99b5b8b..26a575f 100644
--- a/services/powermanager/include/android/WorkDuration.h
+++ b/services/powermanager/include/android/WorkDuration.h
@@ -61,11 +61,11 @@
         return os;
     }
 
-    int64_t workPeriodStartTimestampNanos;
+    int64_t timestampNanos;
     int64_t actualTotalDurationNanos;
+    int64_t workPeriodStartTimestampNanos;
     int64_t actualCpuDurationNanos;
     int64_t actualGpuDurationNanos;
-    int64_t timestampNanos;
 };
 
 } // namespace android::os
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index 641ba9f..3d2cf29 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -29,9 +29,12 @@
 #include <thread>
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::IPower;
 using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::Mode;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionTag;
 using android::binder::Status;
 
 using namespace android;
@@ -53,6 +56,14 @@
                 (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
                  int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, SessionTag tag, SessionConfig* config,
+                 std::shared_ptr<IPowerHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel,
+                (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig
index 6b74f6f..a3bd0ee 100644
--- a/services/sensorservice/senserservice_flags.aconfig
+++ b/services/sensorservice/senserservice_flags.aconfig
@@ -5,4 +5,11 @@
   namespace: "sensors"
   description: "This flag controls if the dynamic sensor data will be clean up after HAL is disconnected."
   bug: "307782607"
+}
+
+flag {
+  name: "sensor_device_on_dynamic_sensor_disconnected"
+  namespace: "sensors"
+  description: "This flag controls if the callback onDynamicSensorsDisconnected is implemented or not."
+  bug: "316958439"
 }
\ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 5e84be1..c71c517 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -40,6 +40,9 @@
     // True if the display is secure
     virtual bool isSecure() const = 0;
 
+    // Sets the secure flag for the display
+    virtual void setSecure(bool secure) = 0;
+
     // True if the display is virtual
     virtual bool isVirtual() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index eac5d97..c53b461 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -74,6 +74,7 @@
     void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
     void createClientCompositionCache(uint32_t cacheSize) override;
     void applyDisplayBrightness(const bool applyImmediately) override;
+    void setSecure(bool secure) override;
 
     // Internal helpers used by chooseCompositionStrategy()
     using ChangedTypes = android::HWComposer::DeviceRequestedChanges::ChangedTypes;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 7e99ec2..46cb95e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -33,6 +33,7 @@
 
     MOCK_CONST_METHOD0(getId, DisplayId());
     MOCK_CONST_METHOD0(isSecure, bool());
+    MOCK_METHOD1(setSecure, void(bool));
     MOCK_CONST_METHOD0(isVirtual, bool());
     MOCK_CONST_METHOD0(getPreferredBootHwcConfigId, int32_t());
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 690d35f..d907bf5 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -74,6 +74,10 @@
     return getState().isSecure;
 }
 
+void Display::setSecure(bool secure) {
+    editState().isSecure = secure;
+}
+
 bool Display::isVirtual() const {
     return VirtualDisplayId::tryCast(mId).has_value();
 }
@@ -246,7 +250,6 @@
     }
 
     // Get any composition changes requested by the HWC device, and apply them.
-    std::optional<android::HWComposer::DeviceRequestedChanges> changes;
     auto& hwc = getCompositionEngine().getHwComposer();
     const bool requiresClientComposition = anyLayersRequireClientComposition();
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index a18397d..91cfe5d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -56,7 +56,7 @@
         }
 
         // Do not unflatten if source crop is only moved.
-        if (FlagManager::getInstance().cache_if_source_crop_layer_only_moved() &&
+        if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() &&
             incomingLayers[i]->isSourceCropSizeEqual(*(existingLayers[i])) &&
             incomingLayers[i]->getDifferingFields(*(existingLayers[i])) ==
                     LayerStateField::SourceCrop) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index bf7ed87..30ba9e4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -4094,7 +4094,11 @@
 };
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = false;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4108,7 +4112,11 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
 
@@ -4130,7 +4138,11 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEverywhere) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4143,7 +4155,11 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4222,6 +4238,7 @@
 
     GenerateClientCompositionRequestsTest() {
         mOutput.mState.needsFiltering = false;
+        mOutput.mState.isProtected = true;
 
         mOutput.setDisplayColorProfileForTest(
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
@@ -4246,6 +4263,7 @@
         mOutput.mState.displaySpace.setOrientation(kDisplayOrientation);
         mOutput.mState.needsFiltering = false;
         mOutput.mState.isSecure = false;
+        mOutput.mState.isProtected = true;
 
         for (size_t i = 0; i < mLayers.size(); i++) {
             mLayers[i].mOutputLayerState.clearClientTarget = false;
@@ -4708,7 +4726,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4721,7 +4739,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4734,7 +4752,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4912,7 +4930,7 @@
             Region(Rect(0, 0, 1000, 1000)),
             false, /* needs filtering */
             true,  /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
@@ -4931,7 +4949,7 @@
             Region(Rect(1000, 0, 2000, 1000)),
             false, /* needs filtering */
             true,  /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 763b998..d2eff75 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -244,7 +244,7 @@
 
 TEST_F(FlattenerTest, unflattenLayers_onlySourceCropMoved) {
     SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
-                              cache_if_source_crop_layer_only_moved,
+                              cache_when_source_crop_layer_only_moved,
                       true);
 
     auto& layerState1 = mTestLayers[0]->layerState;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 950b05e..799d62c 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -353,6 +353,10 @@
     return mCompositionDisplay->isSecure();
 }
 
+void DisplayDevice::setSecure(bool secure) {
+    mCompositionDisplay->setSecure(secure);
+}
+
 const Rect DisplayDevice::getBounds() const {
     return mCompositionDisplay->getState().displaySpace.getBoundsAsRect();
 }
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index ac390cb..97b56a2 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -95,6 +95,7 @@
     // isSecure indicates whether this display can be trusted to display
     // secure surfaces.
     bool isSecure() const;
+    void setSecure(bool secure);
 
     int getWidth() const;
     int getHeight() const;
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 24a9e22..704ece5 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -165,8 +165,7 @@
     auto intError = mComposer.getChangedCompositionTypes(
             mId, &layerIds, &types);
     uint32_t numElements = layerIds.size();
-    auto error = static_cast<Error>(intError);
-    error = static_cast<Error>(intError);
+    const auto error = static_cast<Error>(intError);
     if (error != Error::NONE) {
         return error;
     }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 3ffd8ea..cf1c3c1 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -77,6 +77,8 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using aidl::android::hardware::graphics::composer3::VrrConfig;
+using namespace std::string_literals;
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace android {
@@ -89,7 +91,8 @@
       : mComposer(std::move(composer)),
         mMaxVirtualDisplayDimension(static_cast<size_t>(sysprop::max_virtual_display_dimension(0))),
         mUpdateDeviceProductInfoOnHotplugReconnect(
-                sysprop::update_device_product_info_on_hotplug_reconnect(false)) {}
+                sysprop::update_device_product_info_on_hotplug_reconnect(false)),
+        mEnableVrrTimeout(base::GetBoolProperty("debug.sf.vrr_timeout_hint_enabled"s, false)) {}
 
 HWComposer::HWComposer(const std::string& composerServiceName)
       : HWComposer(Hwc2::Composer::create(composerServiceName)) {}
@@ -299,6 +302,10 @@
             hwcMode.dpiY = config.dpi->y;
         }
 
+        if (!mEnableVrrTimeout) {
+            hwcMode.vrrConfig->notifyExpectedPresentConfig = {};
+        }
+
         modes.push_back(hwcMode);
     }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 4ca528a..fb32ff4 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -491,6 +491,7 @@
 private:
     // For unit tests
     friend TestableSurfaceFlinger;
+    friend HWComposerTest;
 
     struct DisplayData {
         std::unique_ptr<HWC2::Display> hwcDisplay;
@@ -542,6 +543,7 @@
 
     const size_t mMaxVirtualDisplayDimension;
     const bool mUpdateDeviceProductInfoOnHotplugReconnect;
+    bool mEnableVrrTimeout;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 31c2833..e3d9622 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -169,10 +169,8 @@
         out << "}, ";
         out << "notifyExpectedPresentConfig={";
         if (vrrConfig->notifyExpectedPresentConfig) {
-            out << "notifyExpectedPresentHeadsUpNs="
-                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentHeadsUpNs
-                << ", notifyExpectedPresentTimeoutNs="
-                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs;
+            out << "headsUpNs=" << vrrConfig->notifyExpectedPresentConfig->headsUpNs
+                << ", timeoutNs=" << vrrConfig->notifyExpectedPresentConfig->timeoutNs;
         }
         out << "}}";
         return out.str();
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index dd228b4..a0c943b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -31,10 +31,6 @@
 #include <utils/Mutex.h>
 #include <utils/Trace.h>
 
-#include <aidl/android/hardware/power/IPower.h>
-#include <aidl/android/hardware/power/IPowerHintSession.h>
-#include <aidl/android/hardware/power/WorkDuration.h>
-
 #include <binder/IServiceManager.h>
 
 #include "../SurfaceFlingerProperties.h"
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 0276e44..bbe51cc0 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -25,9 +25,14 @@
 #include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 
+// FMQ library in IPower does questionable conversions
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
-#include <compositionengine/impl/OutputCompositionState.h>
 #include <powermanager/PowerHalController.h>
+#pragma clang diagnostic pop
+
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <scheduler/Time.h>
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 803299c..d0e2d7a 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -390,6 +390,22 @@
     mGpuComposition = true;
 }
 
+// TODO(b/316171339): migrate from perfetto side
+bool SurfaceFrame::isSelfJanky() const {
+    int32_t jankType = getJankType().value_or(JankType::None);
+
+    if (jankType == JankType::None) {
+        return false;
+    }
+
+    int32_t jankBitmask = JankType::AppDeadlineMissed | JankType::Unknown;
+    if (jankType & jankBitmask) {
+        return true;
+    }
+
+    return false;
+}
+
 std::optional<int32_t> SurfaceFrame::getJankType() const {
     std::scoped_lock lock(mMutex);
     if (mPresentState == PresentState::Dropped) {
@@ -1113,20 +1129,23 @@
 }
 
 void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                                  nsecs_t previousActualPresentTime) const {
+                                                  nsecs_t previousPredictionPresentTime) const {
     nsecs_t skippedFrameStartTime = 0, skippedFramePresentTime = 0;
     const constexpr float kThresh = 0.5f;
     const constexpr float kRange = 1.5f;
     for (auto& surfaceFrame : mSurfaceFrames) {
-        if (previousActualPresentTime != 0 &&
-            static_cast<float>(mSurfaceFlingerActuals.presentTime - previousActualPresentTime) >=
+        if (previousPredictionPresentTime != 0 &&
+            static_cast<float>(mSurfaceFlingerPredictions.presentTime -
+                               previousPredictionPresentTime) >=
                     static_cast<float>(mRenderRate.getPeriodNsecs()) * kRange &&
             static_cast<float>(surfaceFrame->getPredictions().presentTime) <=
-                    (static_cast<float>(mSurfaceFlingerActuals.presentTime) -
+                    (static_cast<float>(mSurfaceFlingerPredictions.presentTime) -
                      kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
             static_cast<float>(surfaceFrame->getPredictions().presentTime) >=
-                    (static_cast<float>(previousActualPresentTime) -
-                     kThresh * static_cast<float>(mRenderRate.getPeriodNsecs()))) {
+                    (static_cast<float>(previousPredictionPresentTime) -
+                     kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
+            // sf skipped frame is not considered if app is self janked
+            !surfaceFrame->isSelfJanky()) {
             skippedFrameStartTime = surfaceFrame->getPredictions().endTime;
             skippedFramePresentTime = surfaceFrame->getPredictions().presentTime;
             break;
@@ -1215,18 +1234,18 @@
     });
 }
 
-void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                        nsecs_t previousActualPresentTime) const {
+nsecs_t FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                           nsecs_t previousPredictionPresentTime) const {
     if (mSurfaceFrames.empty()) {
         // We don't want to trace display frames without any surface frames updates as this cannot
         // be janky
-        return;
+        return previousPredictionPresentTime;
     }
 
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // DisplayFrame should not have an invalid token.
         ALOGE("Cannot trace DisplayFrame with invalid token");
-        return;
+        return previousPredictionPresentTime;
     }
 
     if (mPredictionState == PredictionState::Valid) {
@@ -1241,8 +1260,9 @@
     }
 
     if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) {
-        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousActualPresentTime);
+        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime);
     }
+    return mSurfaceFlingerPredictions.presentTime;
 }
 
 float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) {
@@ -1333,8 +1353,9 @@
         const auto& pendingPresentFence = *mPendingPresentFences.begin();
         const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
         auto& displayFrame = pendingPresentFence.second;
-        displayFrame->onPresent(signalTime, mPreviousPresentTime);
-        displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, mPreviousPresentTime);
+        displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                                             mPreviousPredictionPresentTime);
         mPendingPresentFences.erase(mPendingPresentFences.begin());
     }
 
@@ -1349,9 +1370,10 @@
         }
 
         auto& displayFrame = pendingPresentFence.second;
-        displayFrame->onPresent(signalTime, mPreviousPresentTime);
-        displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, mPreviousPresentTime);
-        mPreviousPresentTime = signalTime;
+        displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                                             mPreviousPredictionPresentTime);
+        mPreviousActualPresentTime = signalTime;
 
         mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
         --i;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index b5047a3..a76f7d4 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -165,6 +165,8 @@
                  TraceCookieCounter* traceCookieCounter, bool isBuffer, GameMode);
     ~SurfaceFrame() = default;
 
+    bool isSelfJanky() const;
+
     // Returns std::nullopt if the frame hasn't been classified yet.
     // Used by both SF and FrameTimeline.
     std::optional<int32_t> getJankType() const;
@@ -381,8 +383,8 @@
         // Emits a packet for perfetto tracing. The function body will be executed only if tracing
         // is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME
         // and SYSTEM_TIME_MONOTONIC.
-        void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                   nsecs_t previousActualPresentTime) const;
+        nsecs_t trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                      nsecs_t previousPredictionPresentTime) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
         void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
                         std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
@@ -508,7 +510,8 @@
     uint32_t mMaxDisplayFrames;
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
-    nsecs_t mPreviousPresentTime = 0;
+    nsecs_t mPreviousActualPresentTime = 0;
+    nsecs_t mPreviousPredictionPresentTime = 0;
     const JankClassificationThresholds mJankClassificationThresholds;
     static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
     // The initial container size for the vector<SurfaceFrames> inside display frame. Although
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 1e5a6fb..821ac0c 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -190,8 +190,12 @@
     return outInvalidRelativeRoot != UNASSIGNED_LAYER_ID;
 }
 
-LayerHierarchyBuilder::LayerHierarchyBuilder(
-        const std::vector<std::unique_ptr<RequestedLayerState>>& layers) {
+void LayerHierarchyBuilder::init(const std::vector<std::unique_ptr<RequestedLayerState>>& layers) {
+    mLayerIdToHierarchy.clear();
+    mHierarchies.clear();
+    mRoot = nullptr;
+    mOffscreenRoot = nullptr;
+
     mHierarchies.reserve(layers.size());
     mLayerIdToHierarchy.reserve(layers.size());
     for (auto& layer : layers) {
@@ -202,6 +206,7 @@
         onLayerAdded(layer.get());
     }
     detachHierarchyFromRelativeParent(&mOffscreenRoot);
+    mInitialized = true;
 }
 
 void LayerHierarchyBuilder::attachToParent(LayerHierarchy* hierarchy) {
@@ -332,7 +337,7 @@
     }
 }
 
-void LayerHierarchyBuilder::update(
+void LayerHierarchyBuilder::doUpdate(
         const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
         const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers) {
     // rebuild map
@@ -381,6 +386,32 @@
     attachHierarchyToRelativeParent(&mRoot);
 }
 
+void LayerHierarchyBuilder::update(LayerLifecycleManager& layerLifecycleManager) {
+    if (!mInitialized) {
+        ATRACE_NAME("LayerHierarchyBuilder:init");
+        init(layerLifecycleManager.getLayers());
+    } else if (layerLifecycleManager.getGlobalChanges().test(
+                       RequestedLayerState::Changes::Hierarchy)) {
+        ATRACE_NAME("LayerHierarchyBuilder:update");
+        doUpdate(layerLifecycleManager.getLayers(), layerLifecycleManager.getDestroyedLayers());
+    } else {
+        return; // nothing to do
+    }
+
+    uint32_t invalidRelativeRoot;
+    bool hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
+    while (hasRelZLoop) {
+        ATRACE_NAME("FixRelZLoop");
+        TransactionTraceWriter::getInstance().invoke("relz_loop_detected",
+                                                     /*overwrite=*/false);
+        layerLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
+        // reinitialize the hierarchy with the updated layer data
+        init(layerLifecycleManager.getLayers());
+        // check if we have any remaining loops
+        hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
+    }
+}
+
 const LayerHierarchy& LayerHierarchyBuilder::getHierarchy() const {
     return mRoot;
 }
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index ba2e262..a1c73c3 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerLifecycleManager.h"
 #include "RequestedLayerState.h"
 #include "ftl/small_vector.h"
 
@@ -197,9 +198,8 @@
 // hierarchy from a list of RequestedLayerState and associated change flags.
 class LayerHierarchyBuilder {
 public:
-    LayerHierarchyBuilder(const std::vector<std::unique_ptr<RequestedLayerState>>&);
-    void update(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
-                const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers);
+    LayerHierarchyBuilder() = default;
+    void update(LayerLifecycleManager& layerLifecycleManager);
     LayerHierarchy getPartialHierarchy(uint32_t, bool childrenOnly) const;
     const LayerHierarchy& getHierarchy() const;
     const LayerHierarchy& getOffscreenHierarchy() const;
@@ -213,14 +213,18 @@
     void detachFromRelativeParent(LayerHierarchy*);
     void attachHierarchyToRelativeParent(LayerHierarchy*);
     void detachHierarchyFromRelativeParent(LayerHierarchy*);
-
+    void init(const std::vector<std::unique_ptr<RequestedLayerState>>&);
+    void doUpdate(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
+                  const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers);
     void onLayerDestroyed(RequestedLayerState* layer);
     void updateMirrorLayer(RequestedLayerState* layer);
     LayerHierarchy* getHierarchyFromId(uint32_t layerId, bool crashOnFailure = true);
+
     std::unordered_map<uint32_t, LayerHierarchy*> mLayerIdToHierarchy;
     std::vector<std::unique_ptr<LayerHierarchy>> mHierarchies;
     LayerHierarchy mRoot{nullptr};
     LayerHierarchy mOffscreenRoot{nullptr};
+    bool mInitialized = false;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 693a357..6e862b4 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -100,6 +100,11 @@
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
             return StringPrintf("ModeChanged{displayId=%s, modeId=%u}",
                                 to_string(event.header.displayId).c_str(), event.modeChange.modeId);
+        case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+            return StringPrintf("HdcpLevelsChange{displayId=%s, connectedLevel=%d, maxLevel=%d}",
+                                to_string(event.header.displayId).c_str(),
+                                event.hdcpLevelsChange.connectedLevel,
+                                event.hdcpLevelsChange.maxLevel);
         default:
             return "Event{}";
     }
@@ -170,6 +175,20 @@
             }};
 }
 
+DisplayEventReceiver::Event makeHdcpLevelsChange(PhysicalDisplayId displayId,
+                                                 int32_t connectedLevel, int32_t maxLevel) {
+    return DisplayEventReceiver::Event{
+            .header =
+                    DisplayEventReceiver::Event::Header{
+                            .type = DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE,
+                            .displayId = displayId,
+                            .timestamp = systemTime(),
+                    },
+            .hdcpLevelsChange.connectedLevel = connectedLevel,
+            .hdcpLevelsChange.maxLevel = maxLevel,
+    };
+}
+
 } // namespace
 
 EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
@@ -301,7 +320,7 @@
 
     mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(),
                                .readyDuration = mReadyDuration.count(),
-                               .earliestVsync = mLastVsyncCallbackTime.ns()});
+                               .lastVsync = mLastVsyncCallbackTime.ns()});
 }
 
 sp<EventThreadConnection> EventThread::createEventConnection(
@@ -442,6 +461,14 @@
     mCondition.notify_all();
 }
 
+void EventThread::onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                      int32_t maxLevel) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    mPendingEvents.push_back(makeHdcpLevelsChange(displayId, connectedLevel, maxLevel));
+    mCondition.notify_all();
+}
+
 void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
     DisplayEventConsumers consumers;
 
@@ -501,7 +528,7 @@
             const auto scheduleResult =
                     mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                                  .readyDuration = mReadyDuration.count(),
-                                                 .earliestVsync = mLastVsyncCallbackTime.ns()});
+                                                 .lastVsync = mLastVsyncCallbackTime.ns()});
             LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback");
         } else {
             mVsyncRegistration.cancel();
@@ -757,7 +784,7 @@
     if (reschedule) {
         mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                      .readyDuration = mReadyDuration.count(),
-                                     .earliestVsync = mLastVsyncCallbackTime.ns()});
+                                     .lastVsync = mLastVsyncCallbackTime.ns()});
     }
     return oldRegistration;
 }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 7842318..8970103 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -131,6 +131,9 @@
             const sp<EventThreadConnection>& connection) const = 0;
 
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
+
+    virtual void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                     int32_t maxLevel) = 0;
 };
 
 struct IEventThreadCallback {
@@ -177,6 +180,9 @@
 
     void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override EXCLUDES(mMutex);
 
+    void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                             int32_t maxLevel) override;
+
 private:
     friend EventThreadTest;
 
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 18c0a69..cf8b3bf 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -125,7 +125,7 @@
         mVsync.scheduledFrameTime =
                 mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
                                                .readyDuration = 0,
-                                               .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                               .lastVsync = mVsync.lastCallbackTime.ns()});
     }
     return oldRegistration;
 }
@@ -143,7 +143,7 @@
     mVsync.scheduledFrameTime =
             mVsync.registration->update({.workDuration = mVsync.workDuration.get().count(),
                                          .readyDuration = 0,
-                                         .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                         .lastVsync = mVsync.lastCallbackTime.ns()});
 }
 
 void MessageQueue::waitMessage() {
@@ -196,7 +196,7 @@
     mVsync.scheduledFrameTime =
             mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
                                            .readyDuration = 0,
-                                           .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                           .lastVsync = mVsync.lastCallbackTime.ns()});
 }
 
 auto MessageQueue::getScheduledFrameTime() const -> std::optional<Clock::time_point> {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index ce59a04..bfc47e6 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -412,6 +412,17 @@
     thread->onFrameRateOverridesChanged(displayId, std::move(overrides));
 }
 
+void Scheduler::onHdcpLevelsChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
+                                    int32_t connectedLevel, int32_t maxLevel) {
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+    thread->onHdcpLevelsChanged(displayId, connectedLevel, maxLevel);
+}
+
 void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
@@ -600,8 +611,10 @@
     const Display& display = *displayOpt;
     const nsecs_t threshold =
             display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriodNsecs() / 2;
-    const nsecs_t nextVsyncTime = display.schedulePtr->getTracker().nextAnticipatedVSyncTimeFrom(
-            currentExpectedPresentTime.ns() + threshold);
+    const nsecs_t nextVsyncTime =
+            display.schedulePtr->getTracker()
+                    .nextAnticipatedVSyncTimeFrom(currentExpectedPresentTime.ns() + threshold,
+                                                  currentExpectedPresentTime.ns());
     return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns());
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ce585c6..a29d153 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -173,6 +173,8 @@
     void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId)
             EXCLUDES(mConnectionsLock);
 
+    void onHdcpLevelsChanged(ConnectionHandle, PhysicalDisplayId, int32_t, int32_t);
+
     // Modifies work duration in the event thread.
     void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration);
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index c3a952f..f978016 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -84,8 +84,8 @@
      *                 able to provide the ready-by time (deadline) on the callback.
      *                 For internal clients, we don't need to add additional padding, so
      *                 readyDuration will typically be 0.
-     * @earliestVsync: The targeted display time. This will be snapped to the closest
-     *                 predicted vsync time after earliestVsync.
+     * @lastVsync: The targeted display time. This will be snapped to the closest
+     *                 predicted vsync time after lastVsync.
      *
      * callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync
      * event.
@@ -93,11 +93,11 @@
     struct ScheduleTiming {
         nsecs_t workDuration = 0;
         nsecs_t readyDuration = 0;
-        nsecs_t earliestVsync = 0;
+        nsecs_t lastVsync = 0;
 
         bool operator==(const ScheduleTiming& other) const {
             return workDuration == other.workDuration && readyDuration == other.readyDuration &&
-                    earliestVsync == other.earliestVsync;
+                    lastVsync == other.lastVsync;
         }
 
         bool operator!=(const ScheduleTiming& other) const { return !(*this == other); }
@@ -109,12 +109,12 @@
      * The callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync
      * event.
      *
-     * The caller designates the earliest vsync event that should be targeted by the earliestVsync
+     * The caller designates the earliest vsync event that should be targeted by the lastVsync
      * parameter.
      * The callback will be scheduled at (workDuration + readyDuration - predictedVsync), where
-     * predictedVsync is the first vsync event time where ( predictedVsync >= earliestVsync ).
+     * predictedVsync is the first vsync event time where ( predictedVsync >= lastVsync ).
      *
-     * If (workDuration + readyDuration - earliestVsync) is in the past, or if a callback has
+     * If (workDuration + readyDuration - lastVsync) is in the past, or if a callback has
      * already been dispatched for the predictedVsync, an error will be returned.
      *
      * It is valid to reschedule a callback to a different time.
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index ef30887..5cb0ffb 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -45,8 +45,11 @@
 
 nsecs_t getExpectedCallbackTime(VSyncTracker& tracker, nsecs_t now,
                                 const VSyncDispatch::ScheduleTiming& timing) {
-    const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
-            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
+    const auto nextVsyncTime =
+            tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
+                                                          now + timing.workDuration +
+                                                                  timing.readyDuration),
+                                                 timing.lastVsync);
     return getExpectedCallbackTime(nextVsyncTime, timing);
 }
 
@@ -93,8 +96,11 @@
 
 ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing,
                                                       VSyncTracker& tracker, nsecs_t now) {
-    auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
-            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
+    auto nextVsyncTime =
+            tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
+                                                          now + timing.workDuration +
+                                                                  timing.readyDuration),
+                                                 timing.lastVsync);
     auto nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
 
     bool const wouldSkipAVsyncTarget =
@@ -139,11 +145,13 @@
     bool const nextVsyncTooClose = mLastDispatchTime &&
             (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod;
     if (alreadyDispatchedForVsync) {
-        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance);
+        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance,
+                                                    *mLastDispatchTime);
     }
 
     if (nextVsyncTooClose) {
-        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod);
+        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod,
+                                                    *mLastDispatchTime + currentPeriod);
     }
 
     return nextVsyncTime;
@@ -160,11 +168,12 @@
     }
 
     const auto earliestReadyBy = now + mScheduleTiming.workDuration + mScheduleTiming.readyDuration;
-    const auto earliestVsync = std::max(earliestReadyBy, mScheduleTiming.earliestVsync);
+    const auto earliestVsync = std::max(earliestReadyBy, mScheduleTiming.lastVsync);
 
     const auto nextVsyncTime =
             adjustVsyncIfNeeded(tracker, /*nextVsyncTime*/
-                                tracker.nextAnticipatedVSyncTimeFrom(earliestVsync));
+                                tracker.nextAnticipatedVSyncTimeFrom(earliestVsync,
+                                                                     mScheduleTiming.lastVsync));
     const auto nextReadyTime = nextVsyncTime - mScheduleTiming.readyDuration;
     const auto nextWakeupTime = nextReadyTime - mScheduleTiming.workDuration;
 
@@ -214,10 +223,10 @@
     StringAppendF(&result, "\t\t%s: %s %s\n", mName.c_str(),
                   mRunning ? "(in callback function)" : "", armedInfo.c_str());
     StringAppendF(&result,
-                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms earliestVsync: %.2fms relative "
+                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms lastVsync: %.2fms relative "
                   "to now\n",
                   mScheduleTiming.workDuration / 1e6f, mScheduleTiming.readyDuration / 1e6f,
-                  (mScheduleTiming.earliestVsync - systemTime()) / 1e6f);
+                  (mScheduleTiming.lastVsync - systemTime()) / 1e6f);
 
     if (mLastDispatchTime) {
         StringAppendF(&result, "\t\t\tmLastDispatchTime: %.2fms ago\n",
@@ -237,6 +246,7 @@
 
 VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() {
     std::lock_guard lock(mMutex);
+    mRunning = false;
     cancelTimer();
     for (auto& [_, entry] : mCallbacks) {
         ALOGE("Forgot to unregister a callback on VSyncDispatch!");
@@ -305,6 +315,10 @@
     std::vector<Invocation> invocations;
     {
         std::lock_guard lock(mMutex);
+        if (!mRunning) {
+            ALOGD("TimerQueue is not running. Skipping callback.");
+            return;
+        }
         auto const now = mTimeKeeper->now();
         mLastTimerCallback = now;
         for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index e0fb8f9..3d08410 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -148,6 +148,10 @@
 
     std::mutex mutable mMutex;
 
+    // During VSyncDispatchTimerQueue deconstruction, skip timerCallback to
+    // avoid crash
+    bool mRunning = true;
+
     static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
     std::unique_ptr<TimeKeeper> const mTimeKeeper;
     VsyncSchedule::TrackerPtr mTracker;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 0d79a39..28e35de 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -253,7 +253,7 @@
 }
 
 auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence {
-    const auto vsync = nextAnticipatedVSyncTimeFromLocked(timestamp);
+    const auto vsync = snapToVsync(timestamp);
     if (!mLastVsyncSequence) return {vsync, 0};
 
     const auto [slope, _] = getVSyncPredictionModelLocked();
@@ -263,7 +263,7 @@
     return {vsync, vsyncSequence};
 }
 
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
+nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
     auto const [slope, intercept] = getVSyncPredictionModelLocked();
 
     if (mTimestamps.empty()) {
@@ -299,9 +299,29 @@
     return prediction;
 }
 
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
+nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+                                                     std::optional<nsecs_t> lastVsyncOpt) const {
+    ATRACE_CALL();
     std::lock_guard lock(mMutex);
+    const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+    const auto threshold = currentPeriod / 2;
+    const auto minFramePeriod = minFramePeriodLocked().ns();
+    const auto lastFrameMissed =
+            lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold;
+    const nsecs_t baseTime =
+            FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt
+            ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold)
+            : timePoint;
+    const auto vsyncTime = snapToVsyncAlignedWithRenderRate(baseTime);
+    if (FlagManager::getInstance().vrr_config()) {
+        const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
+        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
+        mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
+    }
+    return vsyncTime;
+}
 
+nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const {
     // update the mLastVsyncSequence for reference point
     mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
 
@@ -325,30 +345,12 @@
     }();
 
     if (renderRatePhase == 0) {
-        const auto vsyncTime = mLastVsyncSequence->vsyncTime;
-        if (FlagManager::getInstance().vrr_config()) {
-            const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
-            ATRACE_FORMAT("%s InPhase vsyncIn %.2fms", __func__,
-                          ticks<std::milli, float>(vsyncTimePoint - TimePoint::now()));
-            const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
-            mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
-        }
-        return vsyncTime;
+        return mLastVsyncSequence->vsyncTime;
     }
 
     auto const [slope, intercept] = getVSyncPredictionModelLocked();
     const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
-    const auto nextAnticipatedVsyncTime =
-            nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
-    if (FlagManager::getInstance().vrr_config()) {
-        const auto nextAnticipatedVsyncTimePoint = TimePoint::fromNs(nextAnticipatedVsyncTime);
-        ATRACE_FORMAT("%s outOfPhase vsyncIn %.2fms", __func__,
-                      ticks<std::milli, float>(nextAnticipatedVsyncTimePoint - TimePoint::now()));
-        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
-        mVsyncTrackerCallback.onVsyncGenerated(nextAnticipatedVsyncTimePoint, mDisplayModePtr,
-                                               renderRate);
-    }
-    return nextAnticipatedVsyncTime;
+    return snapToVsync(approximateNextVsync - slope / 2);
 }
 
 /*
@@ -403,7 +405,7 @@
             : std::nullopt;
     ALOGV("%s %s: DisplayMode %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
           to_string(*modePtr).c_str(),
-          timeout ? std::to_string(timeout->notifyExpectedPresentTimeoutNs).c_str() : "N/A");
+          timeout ? std::to_string(timeout->timeoutNs).c_str() : "N/A");
     std::lock_guard lock(mMutex);
 
     mDisplayModePtr = modePtr;
@@ -451,6 +453,7 @@
             if (mLastVsyncSequence) {
                 mLastVsyncSequence->vsyncTime += phase.ns();
             }
+            mPastExpectedPresentTimes.clear();
         }
     }
 }
@@ -468,23 +471,17 @@
                                                  lastConfirmedPresentTime.ns()) /
                                       1e6f);
     }
-    mPastExpectedPresentTimes.push_back(expectedPresentTime);
-
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
+    mPastExpectedPresentTimes.push_back(expectedPresentTime);
 
-    const auto minFramePeriod = minFramePeriodLocked().ns();
     while (!mPastExpectedPresentTimes.empty()) {
         const auto front = mPastExpectedPresentTimes.front().ns();
-        const bool frontIsLastConfirmed =
-                std::abs(front - lastConfirmedPresentTime.ns()) < threshold;
-        const bool frontIsBeforeConfirmed =
-                front < lastConfirmedPresentTime.ns() - minFramePeriod + threshold;
-        if (frontIsLastConfirmed || frontIsBeforeConfirmed) {
+        const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold;
+        if (frontIsBeforeConfirmed) {
             if (CC_UNLIKELY(mTraceOn)) {
                 ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
-                                      static_cast<float>(lastConfirmedPresentTime.ns() -
-                                                         mPastExpectedPresentTimes.front().ns()) /
+                                      static_cast<float>(lastConfirmedPresentTime.ns() - front) /
                                               1e6f);
             }
             mPastExpectedPresentTimes.pop_front();
@@ -508,6 +505,7 @@
             TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod);
 
     ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+    mLastMissedVsync = expectedPresentTime;
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 72a3431..9191003 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -45,7 +45,9 @@
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex);
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+                                         std::optional<nsecs_t> lastVsyncOpt = {}) const final
+            EXCLUDES(mMutex);
     nsecs_t currentPeriod() const final EXCLUDES(mMutex);
     Period minFramePeriod() const final EXCLUDES(mMutex);
     void resetModel() final EXCLUDES(mMutex);
@@ -87,7 +89,8 @@
     size_t next(size_t i) const REQUIRES(mMutex);
     bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
     Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
-    nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
+    nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex);
+    nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex);
     bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex);
     Period minFramePeriodLocked() const REQUIRES(mMutex);
     void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
@@ -120,6 +123,8 @@
     mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
 
     std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex);
+
+    TimePoint mLastMissedVsync GUARDED_BY(mMutex);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 1ed863c..417163f 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -57,9 +57,13 @@
      * is updated.
      *
      * \param [in] timePoint    The point in time after which to estimate a vsync event.
+     * \param [in] lastVsyncOpt The last vsync time used by the client. If provided, the tracker
+     *                          should use that as a reference point when generating the new vsync
+     *                          and avoid crossing the minimal frame period of a VRR display.
      * \return                  A prediction of the timestamp of a vsync event.
      */
-    virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const = 0;
+    virtual nsecs_t nextAnticipatedVSyncTimeFrom(
+            nsecs_t timePoint, std::optional<nsecs_t> lastVsyncOpt = {}) const = 0;
 
     /*
      * The current period of the vsync signal.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 2769e5d..2503efc 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -551,6 +551,12 @@
             base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
     mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
             base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
+
+    // These are set by the HWC implementation to indicate that they will use the workarounds.
+    mIsHotplugErrViaNegVsync =
+            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
+
+    mIsHdcpViaNegVsync = base::GetBoolProperty("debug.sf.hwc_hdcp_via_neg_vsync"s, false);
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -2130,15 +2136,28 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
-    if (FlagManager::getInstance().connected_display()) {
+    if (FlagManager::getInstance().connected_display() && timestamp < 0 &&
+        vsyncPeriod.has_value()) {
         // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
-        if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() &&
-            vsyncPeriod.value() == ~0) {
-            int hotplugErrorCode = static_cast<int32_t>(-timestamp);
-            ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode);
+        if (mIsHotplugErrViaNegVsync && vsyncPeriod.value() == ~0) {
+            const int32_t hotplugErrorCode = static_cast<int32_t>(-timestamp);
+            ALOGD("SurfaceFlinger got hotplugErrorCode=%d for display %" PRIu64, hotplugErrorCode,
+                  hwcDisplayId);
             mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode);
             return;
         }
+
+        if (mIsHdcpViaNegVsync && vsyncPeriod.value() == ~1) {
+            const int32_t value = static_cast<int32_t>(-timestamp);
+            // one byte is good enough to encode android.hardware.drm.HdcpLevel
+            const int32_t maxLevel = (value >> 8) & 0xFF;
+            const int32_t connectedLevel = value & 0xFF;
+            ALOGD("SurfaceFlinger got HDCP level changed: connected=%d, max=%d for "
+                  "display=%" PRIu64,
+                  connectedLevel, maxLevel, hwcDisplayId);
+            updateHdcpLevels(hwcDisplayId, connectedLevel, maxLevel);
+            return;
+        }
     }
 
     ATRACE_NAME(vsyncPeriod
@@ -2372,11 +2391,7 @@
                 mLegacyLayers[layer->sequence] = layer;
             }
         }
-        if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) {
-            ATRACE_NAME("LayerHierarchyBuilder:update");
-            mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(),
-                                          mLayerLifecycleManager.getDestroyedLayers());
-        }
+        mLayerHierarchyBuilder.update(mLayerLifecycleManager);
     }
 
     bool mustComposite = false;
@@ -3432,9 +3447,10 @@
     }
 
     const sp<IBinder> token = sp<BBinder>::make();
+    const ui::DisplayConnectionType connectionType =
+            getHwComposer().getDisplayConnectionType(displayId);
 
-    mPhysicalDisplays.try_emplace(displayId, token, displayId,
-                                  getHwComposer().getDisplayConnectionType(displayId),
+    mPhysicalDisplays.try_emplace(displayId, token, displayId, connectionType,
                                   std::move(displayModes), std::move(colorModes),
                                   std::move(info.deviceProductInfo));
 
@@ -3442,7 +3458,7 @@
     state.physical = {.id = displayId,
                       .hwcDisplayId = hwcDisplayId,
                       .activeMode = std::move(activeMode)};
-    state.isSecure = true; // All physical displays are currently considered secure.
+    state.isSecure = connectionType == ui::DisplayConnectionType::Internal;
     state.isProtected = true;
     state.displayName = std::move(info.name);
 
@@ -4126,7 +4142,7 @@
         const auto notifyExpectedPresentConfig =
                 modePtr->getVrrConfig()->notifyExpectedPresentConfig;
         if (!notifyExpectedPresentConfig) return std::nullopt;
-        return Period::fromNs(notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
+        return Period::fromNs(notifyExpectedPresentConfig->timeoutNs);
     }();
 
     notifyExpectedPresentIfRequired(modePtr->getPhysicalDisplayId(), vsyncPeriod,
@@ -4248,9 +4264,6 @@
             sp<RegionSamplingThread>::make(*this,
                                            RegionSamplingThread::EnvironmentTimingTunables());
     mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
-
-    mIsHotplugErrViaNegVsync =
-            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
 }
 
 void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) {
@@ -8752,6 +8765,40 @@
     return NO_ERROR;
 }
 
+void SurfaceFlinger::updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel,
+                                      int32_t maxLevel) {
+    if (!FlagManager::getInstance().connected_display()) {
+        return;
+    }
+
+    Mutex::Autolock lock(mStateLock);
+
+    const auto idOpt = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
+    if (!idOpt) {
+        ALOGE("No display found for HDCP level changed event: connected=%d, max=%d for "
+              "display=%" PRIu64,
+              connectedLevel, maxLevel, hwcDisplayId);
+        return;
+    }
+
+    const bool isInternalDisplay =
+            mPhysicalDisplays.get(*idOpt).transform(&PhysicalDisplay::isInternal).value_or(false);
+    if (isInternalDisplay) {
+        ALOGW("Unexpected HDCP level changed for internal display: connected=%d, max=%d for "
+              "display=%" PRIu64,
+              connectedLevel, maxLevel, hwcDisplayId);
+        return;
+    }
+
+    static_cast<void>(mScheduler->schedule([this, displayId = *idOpt, connectedLevel, maxLevel]() {
+        if (const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayId))) {
+            Mutex::Autolock lock(mStateLock);
+            display->setSecure(connectedLevel >= 2 /* HDCP_V1 */);
+        }
+        mScheduler->onHdcpLevelsChanged(mAppConnectionHandle, displayId, connectedLevel, maxLevel);
+    }));
+}
+
 std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
         BufferData& bufferData, const char* layerName, uint64_t transactionId) {
     if (bufferData.buffer &&
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6b44401..72003cd 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -654,6 +654,8 @@
     status_t getStalledTransactionInfo(
             int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result);
 
+    void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel);
+
     // Implements IBinder::DeathRecipient.
     void binderDied(const wp<IBinder>& who) override;
 
@@ -1276,6 +1278,7 @@
         hal::Connection connection = hal::Connection::INVALID;
     };
 
+    bool mIsHdcpViaNegVsync = false;
     bool mIsHotplugErrViaNegVsync = false;
 
     std::mutex mHotplugMutex;
@@ -1469,7 +1472,7 @@
     bool mLegacyFrontEndEnabled = true;
 
     frontend::LayerLifecycleManager mLayerLifecycleManager;
-    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
     frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
 
     std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index ddbf3e4..6a66fff 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -218,6 +218,13 @@
     friend class Singleton<TransactionTracing>;
     std::function<void(const std::string& prefix, bool overwrite)> mWriterFunction =
             [](const std::string&, bool) {};
+    std::atomic<bool> mEnabled{true};
+
+    void doInvoke(const std::string& filename, bool overwrite) {
+        if (mEnabled) {
+            mWriterFunction(filename, overwrite);
+        }
+    };
 
 public:
     void setWriterFunction(
@@ -225,12 +232,15 @@
         mWriterFunction = std::move(function);
     }
     void invoke(const std::string& prefix, bool overwrite) {
-        mWriterFunction(TransactionTracing::getFilePath(prefix), overwrite);
+        doInvoke(TransactionTracing::getFilePath(prefix), overwrite);
     }
     /* pass in a complete file path for testing */
     void invokeForTest(const std::string& filename, bool overwrite) {
-        mWriterFunction(filename, overwrite);
+        doInvoke(filename, overwrite);
     }
+    /* hacky way to avoid generating traces when converting transaction trace to layers trace. */
+    void disable() { mEnabled.store(false); }
+    void enable() { mEnabled.store(true); }
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index c2d1954..617ea2c 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -40,9 +40,24 @@
 namespace android {
 using namespace ftl::flag_operators;
 
+namespace {
+class ScopedTraceDisabler {
+public:
+    ScopedTraceDisabler() { TransactionTraceWriter::getInstance().disable(); }
+    ~ScopedTraceDisabler() { TransactionTraceWriter::getInstance().enable(); }
+};
+} // namespace
+
 bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile,
                                    std::uint32_t traceFlags, LayerTracing& layerTracing,
                                    bool onlyLastEntry) {
+    // We are generating the layers trace by replaying back a set of transactions. If the
+    // transactions have unexpected states, we may generate a transaction trace to debug
+    // the unexpected state. This is silly. So we disable it by poking the
+    // TransactionTraceWriter. This is really a hack since we should manage our depenecies a
+    // little better.
+    ScopedTraceDisabler fatalErrorTraceDisabler;
+
     if (traceFile.entry_size() == 0) {
         ALOGD("Trace file is empty");
         return false;
@@ -52,7 +67,7 @@
 
     // frontend
     frontend::LayerLifecycleManager lifecycleManager;
-    frontend::LayerHierarchyBuilder hierarchyBuilder{{}};
+    frontend::LayerHierarchyBuilder hierarchyBuilder;
     frontend::LayerSnapshotBuilder snapshotBuilder;
     ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
 
@@ -119,12 +134,10 @@
         lifecycleManager.applyTransactions(transactions, /*ignoreUnknownHandles=*/true);
         lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true);
 
-        if (lifecycleManager.getGlobalChanges().test(
-                    frontend::RequestedLayerState::Changes::Hierarchy)) {
-            hierarchyBuilder.update(lifecycleManager.getLayers(),
-                                    lifecycleManager.getDestroyedLayers());
-        }
+        // update hierarchy
+        hierarchyBuilder.update(lifecycleManager);
 
+        // update snapshots
         frontend::LayerSnapshotBuilder::Args args{.root = hierarchyBuilder.getHierarchy(),
                                                   .layerLifecycleManager = lifecycleManager,
                                                   .displays = displayInfos,
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index cb1faee..a27e100 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -122,7 +122,7 @@
     DUMP_READ_ONLY_FLAG(multithreaded_present);
     DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
     DUMP_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency);
-    DUMP_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved);
+    DUMP_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved);
     DUMP_READ_ONLY_FLAG(enable_fro_dependent_features);
     DUMP_READ_ONLY_FLAG(display_protected);
     DUMP_READ_ONLY_FLAG(fp16_client_target);
@@ -195,7 +195,7 @@
 FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
 FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "")
 FLAG_MANAGER_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency, "")
-FLAG_MANAGER_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved,
+FLAG_MANAGER_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved,
                             "debug.sf.cache_source_crop_only_moved")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_fro_dependent_features, "")
 FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 2e1d6ae..2f2895c 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -61,7 +61,7 @@
     bool multithreaded_present() const;
     bool add_sf_skipped_frames_to_trace() const;
     bool use_known_refresh_rate_for_fps_consistency() const;
-    bool cache_if_source_crop_layer_only_moved() const;
+    bool cache_when_source_crop_layer_only_moved() const;
     bool enable_fro_dependent_features() const;
     bool display_protected() const;
     bool fp16_client_target() const;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index b690d8d..649ad25 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -134,13 +134,13 @@
                 dispatch->schedule(tmp,
                                    {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+                                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()});
             },
             "o.o");
     dispatch->schedule(tmp,
                        {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                         .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                        .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+                        .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()});
     dispatch->unregisterCallback(tmp);
     dispatch->cancel(tmp);
 }
@@ -162,20 +162,20 @@
     entry.update(*stubTracker, 0);
     entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
+                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()},
                    *stubTracker, 0);
     entry.disarm();
     entry.ensureNotRunning();
     entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
+                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()},
                    *stubTracker, 0);
     auto const wakeup = entry.wakeupTime();
     auto const ready = entry.readyTime();
     entry.callback(entry.executing(), *wakeup, *ready);
     entry.addPendingWorkloadUpdate({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+                                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()});
     dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
 }
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index fa307e9..114f3b0 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -86,7 +86,10 @@
 
     bool addVsyncTimestamp(nsecs_t /* timestamp */) override { return true; }
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */) const override { return 1; }
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */,
+                                         std::optional<nsecs_t>) const override {
+        return 1;
+    }
 
     nsecs_t currentPeriod() const override { return 1; }
     Period minFramePeriod() const override { return Period::fromNs(currentPeriod()); }
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 1a28b81..b65a2b3 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -100,8 +100,18 @@
   bug: "299201319"
 }
 
+# This flag is broken.
+# See alternative one: cache_when_source_crop_layer_only_moved
+# flag {
+#   name: "cache_if_source_crop_layer_only_moved"
+#   namespace: "core_graphics"
+#   description: "do not flatten layers if source crop is only moved"
+#   bug: "305718400"
+#   is_fixed_read_only: true
+# }
+
 flag {
-  name: "cache_if_source_crop_layer_only_moved"
+  name: "cache_when_source_crop_layer_only_moved"
   namespace: "core_graphics"
   description: "do not flatten layers if source crop is only moved"
   bug: "305718400"
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index ee12276..6671414 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -481,14 +481,17 @@
 
 constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1;
 
-template <typename PhysicalDisplay, int width, int height>
+template <typename PhysicalDisplay, int width, int height,
+          Secure secure = (PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal)
+                  ? Secure::TRUE
+                  : Secure::FALSE>
 struct PhysicalDisplayVariant
-      : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE,
-                       Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
+      : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE, secure,
+                       PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
                        PHYSICAL_DISPLAY_FLAGS>,
         HwcDisplayVariant<PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL,
                           DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height,
-                                         Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
+                                         Async::FALSE, secure, PhysicalDisplay::PRIMARY,
                                          GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
                           PhysicalDisplay> {};
 
@@ -515,6 +518,7 @@
 };
 
 struct TertiaryDisplay {
+    static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External;
     static constexpr Primary PRIMARY = Primary::FALSE;
     static constexpr uint8_t PORT = 253;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1003;
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 8891c06..4e8a609 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -471,7 +471,7 @@
 
     mock::VSyncTracker& mockTracker =
             *static_cast<mock::VSyncTracker*>(&mVsyncSchedule->getTracker());
-    EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _))
             .WillOnce(Return(preferredExpectedPresentationTime));
 
     VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 6edecff..a5c0657 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -21,6 +21,7 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <optional>
 #include <vector>
 
 // StrictMock<T> derives from T and is not marked final, so the destructor of T is expected to be
@@ -82,6 +83,8 @@
         EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE));
         EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
     }
+
+    void setVrrTimeoutHint(bool status) { mHwc.mEnableVrrTimeout = status; }
 };
 
 TEST_F(HWComposerTest, isHeadless) {
@@ -323,6 +326,7 @@
         EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
     }
     {
+        setVrrTimeoutHint(true);
         constexpr int32_t kWidth = 480;
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
@@ -330,10 +334,8 @@
         const hal::VrrConfig vrrConfig =
                 hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
                                .notifyExpectedPresentConfig = hal::VrrConfig::
-                                       NotifyExpectedPresentConfig{.notifyExpectedPresentHeadsUpNs =
-                                                                           ms2ns(30),
-                                                                   .notifyExpectedPresentTimeoutNs =
-                                                                           ms2ns(30)}};
+                                       NotifyExpectedPresentConfig{.headsUpNs = ms2ns(30),
+                                                                   .timeoutNs = ms2ns(30)}};
         hal::DisplayConfiguration displayConfiguration{.configId = kConfigId,
                                                        .width = kWidth,
                                                        .height = kHeight,
@@ -363,9 +365,9 @@
         displayConfiguration.dpi = {kDpi, kDpi};
 
         EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
-                .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
-                                        displayConfiguration}),
-                                Return(HalError::NONE)));
+                .WillRepeatedly(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
+                                              displayConfiguration}),
+                                      Return(HalError::NONE)));
 
         modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
         EXPECT_EQ(modes.size(), size_t{1});
@@ -377,6 +379,10 @@
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
         EXPECT_EQ(modes.front().dpiX, kDpi);
         EXPECT_EQ(modes.front().dpiY, kDpi);
+
+        setVrrTimeoutHint(false);
+        modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.front().vrrConfig->notifyExpectedPresentConfig, std::nullopt);
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 95f1940..2b333f4 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -45,7 +45,8 @@
 
 // reparenting tests
 TEST_F(LayerHierarchyTest, addLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -64,7 +65,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(2, 11);
     reparentLayer(111, 12);
     reparentLayer(1221, 1);
@@ -79,7 +81,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayerToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(2, UNASSIGNED_LAYER_ID);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
@@ -96,7 +99,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(2, UNASSIGNED_LAYER_ID);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     reparentLayer(1221, UNASSIGNED_LAYER_ID);
@@ -115,7 +119,8 @@
 }
 
 TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     destroyLayerHandle(111);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -139,7 +144,8 @@
 }
 
 TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     reparentLayer(11, 1);
 
@@ -154,7 +160,8 @@
 
 // offscreen tests
 TEST_F(LayerHierarchyTest, layerMovesOnscreen) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -170,7 +177,8 @@
 }
 
 TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -187,7 +195,8 @@
 
 // rel-z tests
 TEST_F(LayerHierarchyTest, setRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -200,7 +209,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -216,7 +226,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentToRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -231,7 +242,8 @@
 }
 
 TEST_F(LayerHierarchyTest, setParentAsRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -246,7 +258,8 @@
 }
 
 TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -262,7 +275,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentRelativeLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -294,7 +308,8 @@
 
 // mirror tests
 TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -308,7 +323,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
@@ -324,7 +340,8 @@
 TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) {
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     createLayer(1111, 111);
     createLayer(112, 11);
@@ -340,7 +357,8 @@
 
 // mirror & relatives tests
 TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(111, 12);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
 
@@ -371,7 +389,8 @@
 }
 
 TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(1221, 12);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 12);
 
@@ -401,7 +420,8 @@
 }
 
 TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentRelativeLayer(11, 2);
     reparentLayer(2, UNASSIGNED_LAYER_ID);
@@ -427,7 +447,8 @@
 }
 
 TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(1221, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -462,7 +483,8 @@
 }
 
 TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     updateBackgroundColor(1, 0.5);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -485,7 +507,8 @@
     createLayer(11, 1);
     reparentLayer(1, 11);
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     std::vector<uint32_t> expectedTraversalPath = {};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -502,17 +525,11 @@
     createLayer(11, 1);
     reparentRelativeLayer(11, 2);
     reparentRelativeLayer(2, 11);
-    mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
-
-    // fix loop
-    uint32_t invalidRelativeRoot;
-    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
-    EXPECT_TRUE(hasRelZLoop);
-    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
-    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
-    EXPECT_EQ(invalidRelativeRoot, 11u);
-    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+    LayerHierarchyBuilder hierarchyBuilder;
+    // this call is expected to fix the loop!
+    hierarchyBuilder.update(mLifecycleManager);
+    uint32_t unused;
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused));
 
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -534,16 +551,11 @@
     createLayer(221, 22);
     reparentRelativeLayer(22, 111);
     reparentRelativeLayer(11, 221);
-    mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
-
-    // fix loop
-    uint32_t invalidRelativeRoot;
-    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
-    EXPECT_TRUE(hasRelZLoop);
-    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
-    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
-    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+    LayerHierarchyBuilder hierarchyBuilder;
+    // this call is expected to fix the loop!
+    hierarchyBuilder.update(mLifecycleManager);
+    uint32_t unused;
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused));
 
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 22, 221, 2, 21, 22, 221};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -554,7 +566,8 @@
 }
 
 TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(1, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -568,7 +581,8 @@
 TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) {
     // remove default hierarchy
     mLifecycleManager = LayerLifecycleManager();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     createRootLayer(1);
     destroyLayerHandle(1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -582,7 +596,8 @@
 // traversal path test
 TEST_F(LayerHierarchyTest, traversalPathId) {
     setZ(122, -1);
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     auto checkTraversalPathIdVisitor =
             [](const LayerHierarchy& hierarchy,
                const LayerHierarchy::TraversalPath& traversalPath) -> bool {
@@ -605,7 +620,8 @@
     createLayer(53, 5);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 4, 5, 51, 53};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -639,7 +655,8 @@
     setZ(13, 1);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 13, 12};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -661,7 +678,8 @@
     setLayerStack(2, 10);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {2, 21, 1, 11};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -672,7 +690,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorDisplay) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -687,7 +706,8 @@
 }
 
 TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5));
     setLayerStack(3, 1);
@@ -701,7 +721,8 @@
 }
 
 TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -719,7 +740,8 @@
 }
 
 TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -737,7 +759,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(12, UNASSIGNED_LAYER_ID);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 7e9abce..67e6249 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -176,14 +176,12 @@
     void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
 
     void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
-        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            hierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                    mLifecycleManager.getDestroyedLayers());
-        }
+        hierarchyBuilder.update(mLifecycleManager);
         mLifecycleManager.commitChanges();
 
         // rebuild layer hierarchy from scratch and verify that it matches the updated state.
-        LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
+        LayerHierarchyBuilder newBuilder;
+        newBuilder.update(mLifecycleManager);
         EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
                   getTraversalPath(newBuilder.getHierarchy()));
         EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
@@ -512,10 +510,7 @@
     }
 
     void update(LayerSnapshotBuilder& snapshotBuilder) {
-        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            mHierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                     mLifecycleManager.getDestroyedLayers());
-        }
+        mHierarchyBuilder.update(mLifecycleManager);
         LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
                                         .layerLifecycleManager = mLifecycleManager,
                                         .includeMetadata = false,
@@ -530,7 +525,7 @@
         mLifecycleManager.commitChanges();
     }
 
-    LayerHierarchyBuilder mHierarchyBuilder{{}};
+    LayerHierarchyBuilder mHierarchyBuilder;
 
     DisplayInfos mFrontEndDisplayInfos;
     bool mHasDisplayChanges = false;
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 787fa1c..e9d2319 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -56,7 +56,7 @@
     LayerHistoryIntegrationTest() : LayerSnapshotTestBase() {
         mFlinger.resetScheduler(mScheduler);
         mLifecycleManager = {};
-        mHierarchyBuilder = {{}};
+        mHierarchyBuilder = {};
     }
 
     void updateLayerSnapshotsAndLayerHistory(nsecs_t now) {
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 50cd784..ba32c68 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -58,8 +58,7 @@
 
     void update(LayerSnapshotBuilder& actualBuilder, LayerSnapshotBuilder::Args& args) {
         if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            mHierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                     mLifecycleManager.getDestroyedLayers());
+            mHierarchyBuilder.update(mLifecycleManager);
         }
         args.root = mHierarchyBuilder.getHierarchy();
         actualBuilder.update(args);
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 9aa089f..e9c4d80 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -91,7 +91,7 @@
 TEST_F(MessageQueueTest, commit) {
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
     EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
@@ -105,7 +105,7 @@
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
@@ -124,7 +124,7 @@
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
@@ -151,7 +151,7 @@
     const auto timingAfterCallback =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                      .readyDuration = 0,
-                                                     .earliestVsync = kPresentTime.ns()};
+                                                     .lastVsync = kPresentTime.ns()};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
@@ -163,7 +163,7 @@
     const auto timing =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDifferentDuration.ns(),
                                                      .readyDuration = 0,
-                                                     .earliestVsync = 0};
+                                                     .lastVsync = 0};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index e515895..7fdca71 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -98,7 +98,7 @@
     mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableScheduler* mScheduler =
             new TestableScheduler{mSelector, mSchedulerCallback, mVsyncTrackerCallback};
-    surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+    surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
 
     ConnectionHandle mConnectionHandle;
     MockEventThread* mEventThread;
@@ -584,17 +584,17 @@
     scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
     vrrTracker->addVsyncTimestamp(0);
 
-    // Next frame at refresh rate as no previous frame
-    EXPECT_EQ(refreshRate,
-              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(0)));
-
     EXPECT_EQ(Fps::fromPeriodNsecs(1000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(500)));
+                                             TimePoint::fromNs(1000)));
     EXPECT_EQ(Fps::fromPeriodNsecs(1000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(1500)));
+                                             TimePoint::fromNs(2000)));
 
+    // Not crossing the min frame period
+    EXPECT_EQ(Fps::fromPeriodNsecs(1500),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(2500)));
     // Change render rate
     frameRate = Fps::fromPeriodNsecs(2000);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
@@ -602,10 +602,10 @@
 
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(2500)));
+                                             TimePoint::fromNs(2000)));
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(4500)));
+                                             TimePoint::fromNs(4000)));
 }
 
 TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 7ad97a2..8b16a8a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -53,7 +53,7 @@
         auto vsyncController = std::make_unique<mock::VsyncController>();
         auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         EXPECT_CALL(*vsyncTracker, currentPeriod())
                 .WillRepeatedly(Return(
                         TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
@@ -138,14 +138,14 @@
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
     EXPECT_CALL(*vsyncTracker, currentPeriod())
             .WillRepeatedly(
                     Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
     EXPECT_CALL(*vsyncTracker, minFramePeriod())
             .WillRepeatedly(Return(Period::fromNs(
                     TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
     mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
                             std::move(eventThread), std::move(sfEventThread),
                             std::move(selectorPtr),
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index fc5f2b0..1583f64 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -46,7 +46,7 @@
 
     EXPECT_CALL(static_cast<mock::VSyncTracker&>(
                         mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
-                nextAnticipatedVSyncTimeFrom(_))
+                nextAnticipatedVSyncTimeFrom(_, _))
             .WillRepeatedly(Return(0));
 
     // --------------------------------------------------------------------
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 22cb24b..27030d1 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -303,13 +303,13 @@
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
 
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         EXPECT_CALL(*vsyncTracker, currentPeriod())
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
         EXPECT_CALL(*vsyncTracker, minFramePeriod())
                 .WillRepeatedly(
                         Return(Period::fromNs(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread),
                        std::move(sfEventThread), DefaultDisplayMode{options.displayId},
                        SchedulerCallbackImpl::kNoOp, VsyncTrackerCallbackImpl::kNoOp,
diff --git a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
index 4a83d44..d071ce9 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
@@ -105,4 +105,16 @@
     verifyTraceFile();
 }
 
+// Check we cannot write to file if the trace write is disabled.
+TEST_F(TransactionTraceWriterTest, canDisableTraceWriter) {
+    TransactionTraceWriter::getInstance().disable();
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    EXPECT_NE(access(mFilename.c_str(), F_OK), 0);
+
+    TransactionTraceWriter::getInstance().enable();
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+    verifyTraceFile();
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 6a56353..d891008 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -64,7 +64,7 @@
 public:
     FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {}
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) const final {
         auto const floor = timePoint % mPeriod;
         if (floor == 0) {
             return timePoint;
@@ -77,7 +77,7 @@
 public:
     VRRStubTracker(nsecs_t period) : StubTracker(period) {}
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const final {
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) const final {
         std::lock_guard lock(mMutex);
         auto const normalized_to_base = time_point - mBase;
         auto const floor = (normalized_to_base) % mPeriod;
@@ -117,7 +117,7 @@
         mCallback.schedule(
                 {.workDuration = mWorkload,
                  .readyDuration = mReadyDuration,
-                 .earliestVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration});
+                 .lastVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration});
 
         for (auto i = 0u; i < iterations - 1; i++) {
             std::unique_lock lock(mMutex);
@@ -130,7 +130,7 @@
 
             mCallback.schedule({.workDuration = mWorkload,
                                 .readyDuration = mReadyDuration,
-                                .earliestVsync = last + mWorkload + mReadyDuration});
+                                .lastVsync = last + mWorkload + mReadyDuration});
         }
 
         // wait for the last callback.
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 2047018..4bf58de 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -46,14 +46,14 @@
 class MockVSyncTracker : public mock::VSyncTracker {
 public:
     MockVSyncTracker(nsecs_t period) : mPeriod{period} {
-        ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_))
+        ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_, _))
                 .WillByDefault(Invoke(this, &MockVSyncTracker::nextVSyncTime));
         ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true));
         ON_CALL(*this, currentPeriod())
                 .WillByDefault(Invoke(this, &MockVSyncTracker::getCurrentPeriod));
     }
 
-    nsecs_t nextVSyncTime(nsecs_t timePoint) const {
+    nsecs_t nextVSyncTime(nsecs_t timePoint, std::optional<nsecs_t>) const {
         if (timePoint % mPeriod == 0) {
             return timePoint;
         }
@@ -243,10 +243,9 @@
                                                           mDispatchGroupThreshold,
                                                           mVsyncMoveThreshold);
         CountingCallback cb(mDispatch);
-        const auto result = mDispatch->schedule(cb,
-                                                {.workDuration = 100,
-                                                 .readyDuration = 0,
-                                                 .earliestVsync = 1000});
+        const auto result =
+                mDispatch->schedule(cb,
+                                    {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
         EXPECT_TRUE(result.has_value());
         EXPECT_EQ(900, *result);
     }
@@ -257,10 +256,9 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = intended});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 
@@ -277,16 +275,14 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    auto result = mDispatch->schedule(cb,
-                                      {.workDuration = 100,
-                                       .readyDuration = 0,
-                                       .earliestVsync = intended});
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 
     result =
-            mDispatch->update(cb,
-                              {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+            mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(700, *result);
 
@@ -303,17 +299,18 @@
 
     CountingCallback cb(mDispatch);
     const auto result =
-            mDispatch->update(cb,
-                              {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+            mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
     EXPECT_FALSE(result.has_value());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(mPeriod)))
+            .WillOnce(Return(1150));
     EXPECT_CALL(mMockClock, alarmAt(_, 1050));
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -324,7 +321,8 @@
     auto const now = 234;
     mMockClock.advanceBy(234);
     auto const workDuration = 10 * mPeriod;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(now + workDuration, std::optional<nsecs_t>(mPeriod)))
             .WillOnce(Return(mPeriod * 11));
     EXPECT_CALL(mMockClock, alarmAt(_, mPeriod));
 
@@ -332,7 +330,7 @@
     const auto result = mDispatch->schedule(cb,
                                             {.workDuration = workDuration,
                                              .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+                                             .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod, *result);
 }
@@ -342,10 +340,9 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
     EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled);
@@ -356,10 +353,9 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
     mMockClock.advanceBy(950);
@@ -371,10 +367,9 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s));
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
 
@@ -393,10 +388,9 @@
 
     PausingCallback cb(mDispatch, 50ms);
     cb.stashResource(resource);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
 
@@ -413,7 +407,8 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .Times(4)
             .WillOnce(Return(1055))
             .WillOnce(Return(1063))
@@ -428,8 +423,8 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
-    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -441,7 +436,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(4)
             .WillOnce(Return(1000))
             .WillOnce(Return(2000))
@@ -455,21 +450,21 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 0});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
     EXPECT_THAT(cb.mCalls[0], Eq(1000));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(2));
     EXPECT_THAT(cb.mCalls[1], Eq(2000));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
 
     advanceToNextCallback();
 
@@ -478,7 +473,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(4)
             .WillOnce(Return(10000))
             .WillOnce(Return(1000))
@@ -493,9 +488,8 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0,
-                        {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10});
-    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod * 10});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
     mDispatch->cancel(cb1);
 }
 
@@ -507,9 +501,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -522,9 +516,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -542,10 +536,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1,
-                        {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = closeOffset, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
     ASSERT_THAT(cb0.mCalls.size(), Eq(1));
@@ -553,11 +546,9 @@
     ASSERT_THAT(cb1.mCalls.size(), Eq(1));
     EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod));
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 2000});
     mDispatch->schedule(cb1,
-                        {.workDuration = notCloseOffset,
-                         .readyDuration = 0,
-                         .earliestVsync = 2000});
+                        {.workDuration = notCloseOffset, .readyDuration = 0, .lastVsync = 2000});
     advanceToNextCallback();
     ASSERT_THAT(cb1.mCalls.size(), Eq(2));
     EXPECT_THAT(cb1.mCalls[1], Eq(2000));
@@ -577,32 +568,32 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
     EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(3)
             .WillOnce(Return(950))
             .WillOnce(Return(1975))
             .WillOnce(Return(2950));
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 920});
 
     mMockClock.advanceBy(850);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1900});
     mMockClock.advanceBy(900);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
     mMockClock.advanceBy(125);
     EXPECT_THAT(cb.mCalls.size(), Eq(2));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2900});
     mMockClock.advanceBy(975);
     EXPECT_THAT(cb.mCalls.size(), Eq(3));
 }
@@ -616,13 +607,11 @@
     tmp = mDispatch->registerCallback(
             [&](auto, auto, auto) {
                 mDispatch->schedule(tmp,
-                                    {.workDuration = 100,
-                                     .readyDuration = 0,
-                                     .earliestVsync = 2000});
+                                    {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
             },
             "o.o");
 
-    mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -631,30 +620,29 @@
     std::optional<nsecs_t> lastTarget;
     tmp = mDispatch->registerCallback(
             [&](auto timestamp, auto, auto) {
-                auto result =
-                        mDispatch->schedule(tmp,
-                                            {.workDuration = 400,
-                                             .readyDuration = 0,
-                                             .earliestVsync = timestamp - mVsyncMoveThreshold});
+                auto result = mDispatch->schedule(tmp,
+                                                  {.workDuration = 400,
+                                                   .readyDuration = 0,
+                                                   .lastVsync = timestamp - mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 result = mDispatch->schedule(tmp,
                                              {.workDuration = 400,
                                               .readyDuration = 0,
-                                              .earliestVsync = timestamp});
+                                              .lastVsync = timestamp});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 result = mDispatch->schedule(tmp,
                                              {.workDuration = 400,
                                               .readyDuration = 0,
-                                              .earliestVsync = timestamp + mVsyncMoveThreshold});
+                                              .lastVsync = timestamp + mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 lastTarget = timestamp;
             },
             "oo");
 
-    mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
     EXPECT_THAT(lastTarget, Eq(1000));
 
@@ -670,16 +658,16 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .lastVsync = 1000});
 
     mMockClock.advanceBy(750);
-    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 2000});
 
     mMockClock.advanceBy(800);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, lateModifications) {
@@ -692,12 +680,12 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000});
-    mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .lastVsync = 2000});
+    mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -709,8 +697,8 @@
 
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 20000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) {
@@ -720,17 +708,15 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
 
     CountingCallback cb0(mDispatch);
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     mDispatch->cancel(cb0);
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) {
     VSyncDispatch::CallbackToken token(100);
     EXPECT_FALSE(
-            mDispatch
-                    ->schedule(token,
-                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
+            mDispatch->schedule(token, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000})
                     .has_value());
     EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error));
 }
@@ -738,12 +724,10 @@
 TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 }
@@ -755,14 +739,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1200, *result);
 
@@ -779,14 +761,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 400)).InSequence(seq);
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(400, *result);
 
@@ -795,19 +775,18 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1002));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(602, *result);
 }
@@ -815,13 +794,12 @@
 TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
+    result =
+            mDispatch->schedule(cb0, {.workDuration = 1100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 }
@@ -832,13 +810,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq);
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
+    result =
+            mDispatch->schedule(cb0, {.workDuration = 1900, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1100, *result);
 }
@@ -850,13 +827,11 @@
 
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
@@ -872,13 +847,11 @@
 
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(0, *result);
 
@@ -892,10 +865,10 @@
     VSyncCallbackRegistration cb(
             mDispatch, [](auto, auto, auto) {}, "");
     VSyncCallbackRegistration cb1(std::move(cb));
-    cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     cb.cancel();
 
-    cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     cb1.cancel();
 }
 
@@ -908,10 +881,10 @@
     VSyncCallbackRegistration cb1(
             mDispatch, [](auto, auto, auto) {}, "");
     cb1 = std::move(cb);
-    cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     cb.cancel();
 
-    cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     cb1.cancel();
 }
 
@@ -924,16 +897,14 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
     mMockClock.advanceBy(80);
@@ -952,16 +923,14 @@
     CountingCallback cb(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb, {.workDuration = 370, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1630, *result);
     mMockClock.advanceBy(80);
@@ -978,12 +947,10 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
 
@@ -1007,12 +974,10 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
 
@@ -1034,21 +999,21 @@
     CountingCallback cb2(mDispatch);
 
     Sequence seq;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(1000));
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(1000));
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb2, {.workDuration = 390, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(610, *result);
 
@@ -1070,10 +1035,9 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 70,
-                                             .readyDuration = 30,
-                                             .earliestVsync = intended});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 70, .readyDuration = 30, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
     advanceToNextCallback();
@@ -1094,8 +1058,8 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
@@ -1118,8 +1082,8 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
@@ -1139,14 +1103,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(300);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1200, *result);
 
@@ -1162,14 +1124,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 300)).InSequence(seq);
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(300);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(300, *result);
 
@@ -1204,7 +1164,7 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
@@ -1219,14 +1179,15 @@
     auto const duration = 500;
     auto const now = 8750;
 
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(now + duration, std::optional<nsecs_t>(994)))
             .Times(1)
             .WillOnce(Return(10000));
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994},
+    EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 994},
                                *mStubTracker.get(), now)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
@@ -1249,7 +1210,7 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
@@ -1272,7 +1233,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1020));
@@ -1284,7 +1245,7 @@
     entry.update(*mStubTracker.get(), 0);
     EXPECT_FALSE(entry.wakeupTime());
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto wakeup = entry.wakeupTime();
@@ -1300,7 +1261,7 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     entry.update(*mStubTracker.get(), 0);
@@ -1313,24 +1274,24 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     entry.executing(); // 1000 is executing
     // had 1000 not been executing, this could have been scheduled for time 800.
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1950));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 1001},
                                *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
@@ -1343,23 +1304,25 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     Sequence seq;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold,
+                                             std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
 
     entry.executing(); // 1000 is executing
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
 }
@@ -1367,16 +1330,16 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
 }
@@ -1386,9 +1349,9 @@
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
     EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
-    entry.addPendingWorkloadUpdate({.workDuration = 100, .readyDuration = 0, .earliestVsync = 400});
+    entry.addPendingWorkloadUpdate({.workDuration = 100, .readyDuration = 0, .lastVsync = 400});
     entry.addPendingWorkloadUpdate(
-            {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400});
+            {.workDuration = effectualOffset, .readyDuration = 0, .lastVsync = 400});
     EXPECT_TRUE(entry.hasPendingWorkloadUpdate());
     entry.update(*mStubTracker.get(), 0);
     EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
@@ -1410,7 +1373,7 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 7a498c9..961ba57 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -662,7 +662,7 @@
 
     const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
     NotifyExpectedPresentConfig notifyExpectedPresentConfig;
-    notifyExpectedPresentConfig.notifyExpectedPresentTimeoutNs = Period::fromNs(30).ns();
+    notifyExpectedPresentConfig.timeoutNs = Period::fromNs(30).ns();
 
     hal::VrrConfig vrrConfig;
     vrrConfig.notifyExpectedPresentConfig = notifyExpectedPresentConfig;
@@ -720,15 +720,17 @@
     vrrTracker.setRenderRate(minFrameRate);
     vrrTracker.addVsyncTimestamp(0);
     EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
-    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000));
 
     vrrTracker.onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
-    EXPECT_EQ(1500, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
-    EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
+    EXPECT_EQ(3500, vrrTracker.nextAnticipatedVSyncTimeFrom(2000, 2000));
+    EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(3500, 3500));
 
-    vrrTracker.onFrameMissed(TimePoint::fromNs(2500));
-    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
-    EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3300));
+    // Miss when starting 4500 and expect the next vsync will be at 5000 (next one)
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3500), TimePoint::fromNs(2500));
+    vrrTracker.onFrameMissed(TimePoint::fromNs(4500));
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
index a088aab..ed1405b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
@@ -18,12 +18,21 @@
 
 #include "binder/Status.h"
 
+// FMQ library in IPower does questionable conversions
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
+#pragma clang diagnostic pop
+
 #include <gmock/gmock.h>
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::IPower;
 using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionTag;
+
 using aidl::android::hardware::power::Mode;
 using android::binder::Status;
 
@@ -42,6 +51,14 @@
                  int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
     MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, SessionTag tag, SessionConfig* config,
+                 std::shared_ptr<IPowerHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel,
+                (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
index 364618d..27564b2 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -18,10 +18,15 @@
 
 #include "binder/Status.h"
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
+#pragma clang diagnostic pop
+
 #include <gmock/gmock.h>
 
 using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
 using aidl::android::hardware::power::SessionHint;
 using aidl::android::hardware::power::SessionMode;
 using android::binder::Status;
@@ -47,6 +52,7 @@
     MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override));
     MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override));
     MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionConfig, (SessionConfig * _aidl_return), (override));
 };
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
index 68fe3c5..b17c8ad 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -19,7 +19,10 @@
 #include <gmock/gmock.h>
 #include <scheduler/Time.h>
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <powermanager/PowerHalController.h>
+#pragma clang diagnostic pop
 
 namespace android {
 namespace hardware {
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 866af3b..e2b0ed1 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -59,6 +59,9 @@
     MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
     MOCK_METHOD(void, pauseVsyncCallback, (bool));
     MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
+    MOCK_METHOD(void, onHdcpLevelsChanged,
+                (PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel),
+                (override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index e588bb9..3870983 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -28,7 +28,8 @@
     ~VSyncTracker() override;
 
     MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override));
-    MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t), (const, override));
+    MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional<nsecs_t>),
+                (const, override));
     MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override));
     MOCK_METHOD(Period, minFramePeriod, (), (const, override));
     MOCK_METHOD(void, resetModel, (), (override));