Merge "Implement virtual stylus" into main
diff --git a/cmds/cmd/cmd.cpp b/cmds/cmd/cmd.cpp
index 0ce7711..9695e07 100644
--- a/cmds/cmd/cmd.cpp
+++ b/cmds/cmd/cmd.cpp
@@ -95,7 +95,7 @@
             flags = O_RDWR;
             checkRead = checkWrite = true;
         } else {
-            mErrorLog << "Invalid mode requested: " << mode.c_str() << endl;
+            mErrorLog << "Invalid mode requested: " << mode << endl;
             return -EINVAL;
         }
         int fd = open(fullPath.c_str(), flags, S_IRWXU|S_IRWXG);
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index b99f443..0bbd4a8 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -246,7 +246,7 @@
 static const std::string DUMP_HALS_TASK = "DUMP HALS";
 static const std::string DUMP_BOARD_TASK = "dumpstate_board()";
 static const std::string DUMP_CHECKINS_TASK = "DUMP CHECKINS";
-static const std::string POST_PROCESS_UI_TRACES_TASK = "POST-PROCESS UI TRACES";
+static const std::string SERIALIZE_PERFETTO_TRACE_TASK = "SERIALIZE PERFETTO TRACE";
 
 namespace android {
 namespace os {
@@ -1086,11 +1086,11 @@
 
 static void MaybeAddSystemTraceToZip() {
     // This function copies into the .zip the system trace that was snapshotted
-    // by the early call to MaybeSnapshotSystemTrace(), if any background
+    // by the early call to MaybeSnapshotSystemTraceAsync(), if any background
     // tracing was happening.
     bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
     if (!system_trace_exists) {
-        // No background trace was happening at the time MaybeSnapshotSystemTrace() was invoked.
+        // No background trace was happening at the time MaybeSnapshotSystemTraceAsync() was invoked
         if (!PropertiesHelper::IsUserBuild()) {
             MYLOGI(
                 "No system traces found. Check for previously uploaded traces by looking for "
@@ -1641,7 +1641,7 @@
 
     // Enqueue slow functions into the thread pool, if the parallel run is enabled.
     std::future<std::string> dump_hals, dump_incident_report, dump_board, dump_checkins,
-            dump_netstats_report, post_process_ui_traces;
+        dump_netstats_report;
     if (ds.dump_pool_) {
         // Pool was shutdown in DumpstateDefaultAfterCritical method in order to
         // drop root user. Restarts it.
@@ -3077,8 +3077,9 @@
 }
 
 void Dumpstate::PreDumpUiData() {
-    MaybeSnapshotSystemTrace();
+    auto snapshot_system_trace = MaybeSnapshotSystemTraceAsync();
     MaybeSnapshotUiTraces();
+    MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace));
 }
 
 /*
@@ -3264,13 +3265,15 @@
     // duration is logged into MYLOG instead.
     PrintHeader();
 
+    std::future<std::string> snapshot_system_trace;
+
     bool is_dumpstate_restricted =
         options_->telephony_only || options_->wifi_only || options_->limited_only;
     if (!is_dumpstate_restricted) {
         // Snapshot the system trace now (if running) to avoid that dumpstate's
         // own activity pushes out interesting data from the trace ring buffer.
         // The trace file is added to the zip by MaybeAddSystemTraceToZip().
-        MaybeSnapshotSystemTrace();
+        snapshot_system_trace = MaybeSnapshotSystemTraceAsync();
 
         // Invoke critical dumpsys to preserve system state, before doing anything else.
         RunDumpsysCritical();
@@ -3281,6 +3284,7 @@
     }
 
     MaybeTakeEarlyScreenshot();
+    MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace));
     onUiIntensiveBugreportDumpsFinished(calling_uid);
     MaybeCheckUserConsent(calling_uid, calling_package);
     if (options_->telephony_only) {
@@ -3376,31 +3380,59 @@
     TakeScreenshot();
 }
 
-void Dumpstate::MaybeSnapshotSystemTrace() {
+std::future<std::string> Dumpstate::MaybeSnapshotSystemTraceAsync() {
     // When capturing traces via bugreport handler (BH), this function will be invoked twice:
     // 1) When BH invokes IDumpstate::PreDumpUiData()
     // 2) When BH invokes IDumpstate::startBugreport(flags = BUGREPORT_USE_PREDUMPED_UI_DATA)
     // In this case we don't want to re-invoke perfetto in step 2.
     // In all other standard invocation states, this function is invoked once
     // without the flag BUGREPORT_USE_PREDUMPED_UI_DATA.
+    // This function must run asynchronously to avoid delaying MaybeTakeEarlyScreenshot() in the
+    // standard invocation states (b/316110955).
     if (options_->use_predumped_ui_data) {
-        return;
+        return {};
+    }
+
+    // Create temporary file for the command's output
+    std::string outPath = ds.bugreport_internal_dir_ + "/tmp_serialize_perfetto_trace";
+    auto outFd = android::base::unique_fd(TEMP_FAILURE_RETRY(
+        open(outPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+             S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
+    if (outFd < 0) {
+        MYLOGE("Could not open %s to serialize perfetto trace.\n", outPath.c_str());
+        return {};
     }
 
     // If a stale file exists already, remove it.
     unlink(SYSTEM_TRACE_SNAPSHOT);
 
-    // If a background system trace is happening and is marked as "suitable for
-    // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
-    // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
-    // case that no trace is ongoing, this command is a no-op.
-    // Note: this should not be enqueued as we need to freeze the trace before
-    // dumpstate starts. Otherwise the trace ring buffers will contain mostly
-    // the dumpstate's own activity which is irrelevant.
-    RunCommand("SERIALIZE PERFETTO TRACE", {"perfetto", "--save-for-bugreport"},
-               CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build());
-    // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
-    // file in the later stages.
+    MYLOGI("Launching async '%s'", SERIALIZE_PERFETTO_TRACE_TASK.c_str())
+    return std::async(
+        std::launch::async, [this, outPath = std::move(outPath), outFd = std::move(outFd)] {
+            // If a background system trace is happening and is marked as "suitable for
+            // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
+            // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
+            // case that no trace is ongoing, this command is a no-op.
+            // Note: this should not be enqueued as we need to freeze the trace before
+            // dumpstate starts. Otherwise the trace ring buffers will contain mostly
+            // the dumpstate's own activity which is irrelevant.
+            RunCommand(
+                SERIALIZE_PERFETTO_TRACE_TASK, {"perfetto", "--save-for-bugreport"},
+                CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build(),
+                false, outFd);
+            // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
+            // file in the later stages.
+
+            return outPath;
+        });
+}
+
+void Dumpstate::MaybeWaitForSnapshotSystemTrace(std::future<std::string> task) {
+    if (!task.valid()) {
+        return;
+    }
+
+    WaitForTask(std::move(task), SERIALIZE_PERFETTO_TRACE_TASK, STDOUT_FILENO);
 }
 
 void Dumpstate::MaybeSnapshotUiTraces() {
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index c66fd1c..20b2865 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -567,7 +567,8 @@
     RunStatus dumpstate();
 
     void MaybeTakeEarlyScreenshot();
-    void MaybeSnapshotSystemTrace();
+    std::future<std::string> MaybeSnapshotSystemTraceAsync();
+    void MaybeWaitForSnapshotSystemTrace(std::future<std::string> task);
     void MaybeSnapshotUiTraces();
     void MaybeAddUiTracesToZip();
 
diff --git a/cmds/idlcli/vibrator.h b/cmds/idlcli/vibrator.h
index dfbb886..e100eac 100644
--- a/cmds/idlcli/vibrator.h
+++ b/cmds/idlcli/vibrator.h
@@ -74,7 +74,7 @@
 }
 
 template <typename I>
-using shared_ptr = std::result_of_t<decltype(getService<I>)&(std::string)>;
+using shared_ptr = std::invoke_result_t<decltype(getService<I>)&, std::string>;
 
 template <typename I>
 class HalWrapper {
diff --git a/cmds/installd/OWNERS b/cmds/installd/OWNERS
index 643b2c2..e9fb85b 100644
--- a/cmds/installd/OWNERS
+++ b/cmds/installd/OWNERS
@@ -1,11 +1,10 @@
 set noparent
 
-calin@google.com
 jsharkey@android.com
 maco@google.com
 mast@google.com
+jiakaiz@google.com
 narayan@google.com
 ngeoffray@google.com
 rpl@google.com
-toddke@google.com
 patb@google.com
diff --git a/cmds/lshal/Timeout.h b/cmds/lshal/Timeout.h
index 805e8dc..d97ba89 100644
--- a/cmds/lshal/Timeout.h
+++ b/cmds/lshal/Timeout.h
@@ -16,83 +16,44 @@
 
 #pragma once
 
-#include <condition_variable>
 #include <chrono>
-#include <functional>
-#include <mutex>
-#include <thread>
+#include <future>
 
 #include <hidl/Status.h>
+#include <utils/Errors.h>
 
 namespace android {
 namespace lshal {
 
-class BackgroundTaskState {
-public:
-    explicit BackgroundTaskState(std::function<void(void)> &&func)
-            : mFunc(std::forward<decltype(func)>(func)) {}
-    void notify() {
-        std::unique_lock<std::mutex> lock(mMutex);
-        mFinished = true;
-        lock.unlock();
-        mCondVar.notify_all();
-    }
-    template<class C, class D>
-    bool wait(std::chrono::time_point<C, D> end) {
-        std::unique_lock<std::mutex> lock(mMutex);
-        mCondVar.wait_until(lock, end, [this](){ return this->mFinished; });
-        return mFinished;
-    }
-    void operator()() {
-        mFunc();
-    }
-private:
-    std::mutex mMutex;
-    std::condition_variable mCondVar;
-    bool mFinished = false;
-    std::function<void(void)> mFunc;
-};
-
-void *callAndNotify(void *data) {
-    BackgroundTaskState &state = *static_cast<BackgroundTaskState *>(data);
-    state();
-    state.notify();
-    return nullptr;
-}
-
-template<class R, class P>
-bool timeout(std::chrono::duration<R, P> delay, std::function<void(void)> &&func) {
-    auto now = std::chrono::system_clock::now();
-    BackgroundTaskState state{std::forward<decltype(func)>(func)};
-    pthread_t thread;
-    if (pthread_create(&thread, nullptr, callAndNotify, &state)) {
-        std::cerr << "FATAL: could not create background thread." << std::endl;
-        return false;
-    }
-    bool success = state.wait(now + delay);
-    if (!success) {
-        pthread_kill(thread, SIGINT);
-    }
-    pthread_join(thread, nullptr);
-    return success;
-}
-
+// Call function on interfaceObject and wait for result until the given timeout has reached.
+// Callback functions pass to timeoutIPC() may be executed after the this function
+// has returned, especially if deadline has been reached. Hence, care must be taken when passing
+// data between the background thread and the main thread. See b/311143089.
 template<class R, class P, class Function, class I, class... Args>
-typename std::result_of<Function(I *, Args...)>::type
+typename std::invoke_result<Function, I *, Args...>::type
 timeoutIPC(std::chrono::duration<R, P> wait, const sp<I> &interfaceObject, Function &&func,
            Args &&... args) {
     using ::android::hardware::Status;
-    typename std::result_of<Function(I *, Args...)>::type ret{Status::ok()};
-    auto boundFunc = std::bind(std::forward<Function>(func),
-            interfaceObject.get(), std::forward<Args>(args)...);
-    bool success = timeout(wait, [&ret, &boundFunc] {
-        ret = std::move(boundFunc());
-    });
-    if (!success) {
+
+    // Execute on a background thread but do not defer execution.
+    auto future =
+            std::async(std::launch::async, func, interfaceObject, std::forward<Args>(args)...);
+    auto status = future.wait_for(wait);
+    if (status == std::future_status::ready) {
+        return future.get();
+    }
+
+    // This future belongs to a background thread that we no longer care about.
+    // Putting this in the global list avoids std::future::~future() that may wait for the
+    // result to come back.
+    // This leaks memory, but lshal is a debugging tool, so this is fine.
+    static std::vector<decltype(future)> gDeadPool{};
+    gDeadPool.emplace_back(std::move(future));
+
+    if (status == std::future_status::timeout) {
         return Status::fromStatusT(TIMED_OUT);
     }
-    return ret;
+    return Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE, "Illegal future_status");
 }
-
-}  // namespace lshal
-}  // namespace android
+} // namespace lshal
+} // namespace android
diff --git a/cmds/lshal/main.cpp b/cmds/lshal/main.cpp
index 366c938..bd5fa32 100644
--- a/cmds/lshal/main.cpp
+++ b/cmds/lshal/main.cpp
@@ -18,5 +18,6 @@
 
 int main(int argc, char **argv) {
     using namespace ::android::lshal;
-    return Lshal{}.main(Arg{argc, argv});
+    // Use _exit() to force terminate background threads in Timeout.h
+    _exit(Lshal{}.main(Arg{argc, argv}));
 }
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index cba7c4b..c24f827 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#include <chrono>
+#include <future>
+#include <mutex>
+#include "android/hidl/base/1.0/IBase.h"
 #define LOG_TAG "Lshal"
 #include <android-base/logging.h>
 
@@ -36,6 +40,8 @@
 
 using namespace testing;
 
+using std::chrono_literals::operator""ms;
+
 using ::android::hidl::base::V1_0::DebugInfo;
 using ::android::hidl::base::V1_0::IBase;
 using ::android::hidl::manager::V1_0::IServiceManager;
@@ -934,12 +940,9 @@
         return hardware::Void();
     }));
     EXPECT_CALL(*serviceManager, get(_, _))
-            .WillRepeatedly(
-                    Invoke([&](const hidl_string&, const hidl_string& instance) -> sp<IBase> {
-                        int id = getIdFromInstanceName(instance);
-                        if (id > inheritanceLevel) return nullptr;
-                        return sp<IBase>(service);
-                    }));
+            .WillRepeatedly(Invoke([&](const hidl_string&, const hidl_string&) -> sp<IBase> {
+                return sp<IBase>(service);
+            }));
 
     const std::string expected = "[fake description 0]\n"
                                  "Interface\n"
@@ -957,6 +960,110 @@
     EXPECT_EQ("", err.str());
 }
 
+// In SlowService, everything goes slooooooow. Each IPC call will wait for
+// the specified time before calling the callback function or returning.
+class SlowService : public IBase {
+public:
+    explicit SlowService(std::chrono::milliseconds wait) : mWait(wait) {}
+    android::hardware::Return<void> interfaceDescriptor(interfaceDescriptor_cb cb) override {
+        std::this_thread::sleep_for(mWait);
+        cb(getInterfaceName(1));
+        storeHistory("interfaceDescriptor");
+        return hardware::Void();
+    }
+    android::hardware::Return<void> interfaceChain(interfaceChain_cb cb) override {
+        std::this_thread::sleep_for(mWait);
+        std::vector<hidl_string> ret;
+        ret.push_back(getInterfaceName(1));
+        ret.push_back(IBase::descriptor);
+        cb(ret);
+        storeHistory("interfaceChain");
+        return hardware::Void();
+    }
+    android::hardware::Return<void> getHashChain(getHashChain_cb cb) override {
+        std::this_thread::sleep_for(mWait);
+        std::vector<hidl_hash> ret;
+        ret.push_back(getHashFromId(0));
+        ret.push_back(getHashFromId(0xff));
+        cb(ret);
+        storeHistory("getHashChain");
+        return hardware::Void();
+    }
+    android::hardware::Return<void> debug(const hidl_handle&,
+                                          const hidl_vec<hidl_string>&) override {
+        std::this_thread::sleep_for(mWait);
+        storeHistory("debug");
+        return Void();
+    }
+
+    template <class R, class P, class Pred>
+    bool waitForHistory(std::chrono::duration<R, P> wait, Pred predicate) {
+        std::unique_lock<std::mutex> lock(mLock);
+        return mCv.wait_for(lock, wait, [&]() { return predicate(mCallHistory); });
+    }
+
+private:
+    void storeHistory(std::string hist) {
+        {
+            std::lock_guard<std::mutex> lock(mLock);
+            mCallHistory.emplace_back(std::move(hist));
+        }
+        mCv.notify_all();
+    }
+
+    const std::chrono::milliseconds mWait;
+    std::mutex mLock;
+    std::condition_variable mCv;
+    // List of functions that have finished being called on this interface.
+    std::vector<std::string> mCallHistory;
+};
+
+class TimeoutTest : public ListTest {
+public:
+    void setMockServiceManager(sp<IBase> service) {
+        EXPECT_CALL(*serviceManager, list(_))
+                .WillRepeatedly(Invoke([&](IServiceManager::list_cb cb) {
+                    std::vector<hidl_string> ret;
+                    ret.push_back(getInterfaceName(1) + "/default");
+                    cb(ret);
+                    return hardware::Void();
+                }));
+        EXPECT_CALL(*serviceManager, get(_, _))
+                .WillRepeatedly(Invoke([&](const hidl_string&, const hidl_string&) -> sp<IBase> {
+                    return service;
+                }));
+    }
+};
+
+TEST_F(TimeoutTest, BackgroundThreadIsKept) {
+    auto lshalIpcTimeout = 100ms;
+    auto serviceIpcTimeout = 200ms;
+    lshal->setWaitTimeForTest(lshalIpcTimeout, lshalIpcTimeout);
+    sp<SlowService> service = new SlowService(serviceIpcTimeout);
+    setMockServiceManager(service);
+
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_NE(0u, mockList->main(createArg({"lshal", "--types=b", "-i", "--neat"})));
+    EXPECT_THAT(err.str(), HasSubstr("Skipping \"a.h.foo1@1.0::IFoo/default\""));
+    EXPECT_TRUE(service->waitForHistory(serviceIpcTimeout * 5, [](const auto& hist) {
+        return hist.size() == 1 && hist[0] == "interfaceChain";
+    })) << "The background thread should continue after the main thread moves on, but it is killed";
+}
+
+TEST_F(TimeoutTest, BackgroundThreadDoesNotBlockMainThread) {
+    auto lshalIpcTimeout = 100ms;
+    auto serviceIpcTimeout = 2000ms;
+    auto start = std::chrono::system_clock::now();
+    lshal->setWaitTimeForTest(lshalIpcTimeout, lshalIpcTimeout);
+    sp<SlowService> service = new SlowService(serviceIpcTimeout);
+    setMockServiceManager(service);
+
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_NE(0u, mockList->main(createArg({"lshal", "--types=b", "-i", "--neat"})));
+    EXPECT_LE(std::chrono::system_clock::now(), start + 5 * lshalIpcTimeout)
+            << "The main thread should not be blocked by the background task";
+}
+
 class ListVintfTest : public ListTest {
 public:
     virtual void SetUp() override {
@@ -1079,5 +1186,6 @@
 
 int main(int argc, char **argv) {
     ::testing::InitGoogleMock(&argc, argv);
-    return RUN_ALL_TESTS();
+    // Use _exit() to force terminate background threads in Timeout.h
+    _exit(RUN_ALL_TESTS());
 }
diff --git a/cmds/servicemanager/servicemanager.rc b/cmds/servicemanager/servicemanager.rc
index 4f92b3a..6c450ab 100644
--- a/cmds/servicemanager/servicemanager.rc
+++ b/cmds/servicemanager/servicemanager.rc
@@ -11,5 +11,5 @@
     onrestart class_restart --only-enabled main
     onrestart class_restart --only-enabled hal
     onrestart class_restart --only-enabled early_hal
-    task_profiles ServiceCapacityLow
+    task_profiles ProcessCapacityHigh
     shutdown critical
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 9d2c791..30200c7 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -123,7 +123,8 @@
   *
   * @return APerformanceHintManager instance on success, nullptr on failure.
   */
-APerformanceHintManager* _Nullable APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+APerformanceHintManager* _Nullable APerformanceHint_getManager()
+                         __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Creates a session for the given set of threads and sets their initial target work
@@ -260,7 +261,8 @@
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
  */
-void AWorkDuration_release(AWorkDuration* _Nonnull WorkDuration) __INTRODUCED_IN(__ANDROID_API_V__);
+void AWorkDuration_release(AWorkDuration* _Nonnull aWorkDuration)
+     __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Sets the work period start timestamp in nanoseconds.
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 0b57e93..fa168cd 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -111,7 +111,7 @@
  * It's passed the updated thermal status as parameter, as well as the
  * pointer provided by the client that registered a callback.
  */
-typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status);
+typedef void (*AThermal_StatusCallback)(void* _Nullable data, AThermalStatus status);
 
 /**
   * Acquire an instance of the thermal manager. This must be freed using
@@ -121,7 +121,7 @@
   *
   * @return manager instance on success, nullptr on failure.
   */
-AThermalManager* AThermal_acquireManager() __INTRODUCED_IN(30);
+AThermalManager* _Nonnull AThermal_acquireManager() __INTRODUCED_IN(30);
 
 /**
  * Release the thermal manager pointer acquired via
@@ -131,7 +131,7 @@
  *
  * @param manager The manager to be released.
  */
-void AThermal_releaseManager(AThermalManager *manager) __INTRODUCED_IN(30);
+void AThermal_releaseManager(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30);
 
 /**
   * Gets the current thermal status.
@@ -143,7 +143,8 @@
   *
   * @return current thermal status, ATHERMAL_STATUS_ERROR on failure.
   */
-AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) __INTRODUCED_IN(30);
+AThermalStatus
+AThermal_getCurrentThermalStatus(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30);
 
 /**
  * Register the thermal status listener for thermal status change.
@@ -160,8 +161,9 @@
  *         EPERM if the required permission is not held.
  *         EPIPE if communication with the system service has failed.
  */
-int AThermal_registerThermalStatusListener(AThermalManager *manager,
-        AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30);
+int AThermal_registerThermalStatusListener(AThermalManager* _Nonnull manager,
+                                           AThermal_StatusCallback _Nullable callback,
+                                           void* _Nullable data) __INTRODUCED_IN(30);
 
 /**
  * Unregister the thermal status listener previously resgistered.
@@ -178,8 +180,9 @@
  *         EPERM if the required permission is not held.
  *         EPIPE if communication with the system service has failed.
  */
-int AThermal_unregisterThermalStatusListener(AThermalManager *manager,
-        AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30);
+int AThermal_unregisterThermalStatusListener(AThermalManager* _Nonnull manager,
+                                             AThermal_StatusCallback _Nullable callback,
+                                             void* _Nullable data) __INTRODUCED_IN(30);
 
 /**
  * Provides an estimate of how much thermal headroom the device currently has before
@@ -219,8 +222,8 @@
  *         as described above. Returns NaN if the device does not support this functionality or
  *         if this function is called significantly faster than once per second.
   */
-float AThermal_getThermalHeadroom(AThermalManager *manager,
-        int forecastSeconds) __INTRODUCED_IN(31);
+float AThermal_getThermalHeadroom(AThermalManager* _Nonnull manager,
+                                  int forecastSeconds) __INTRODUCED_IN(31);
 
 /**
  * This struct defines an instance of headroom threshold value and its status.
@@ -282,9 +285,10 @@
  *         EPIPE if communication with the system service has failed.
  *         ENOSYS if the feature is disabled by the current system.
  */
-int AThermal_getThermalHeadroomThresholds(AThermalManager* manager,
-                                          const AThermalHeadroomThreshold ** outThresholds,
-                                          size_t* size) __INTRODUCED_IN(35);
+int AThermal_getThermalHeadroomThresholds(AThermalManager* _Nonnull manager,
+                                          const AThermalHeadroomThreshold* _Nonnull
+                                          * _Nullable outThresholds,
+                                          size_t* _Nonnull size) __INTRODUCED_IN(35);
 
 #ifdef __cplusplus
 }
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 59b9495..750e170 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -283,6 +283,13 @@
      */
     status_t receiveMessage(InputMessage* msg);
 
+    /* Tells whether there is a message in the channel available to be received.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
     /* Return a new object that has a duplicate of this channel's fd. */
     std::unique_ptr<InputChannel> dup() const;
 
@@ -518,6 +525,13 @@
      */
     int32_t getPendingBatchSource() const;
 
+    /* Returns true when there is *likely* a pending batch or a pending event in the channel.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
     std::string dump() const;
 
 private:
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index d50c5f8..d8f9db4 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -53,6 +53,26 @@
      * CPU resources to what was used previously, and must wake up if inactive.
      */
     CPU_LOAD_RESUME = 3,
+
+    /**
+     * This hint indicates an increase in GPU workload intensity. It means that
+     * this hint session needs extra GPU resources to meet the target duration.
+     * This hint must be sent before reporting the actual duration to the session.
+     */
+    GPU_LOAD_UP = 5,
+
+    /**
+     * This hint indicates a decrease in GPU workload intensity. It means that
+     * this hint session can reduce GPU resources and still meet the target duration.
+     */
+    GPU_LOAD_DOWN = 6,
+
+    /*
+     * This hint indicates an upcoming GPU workload that is completely changed and
+     * unknown. It means that the hint session should reset GPU resources to a known
+     * baseline to prepare for an arbitrary load, and must wake up if inactive.
+     */
+    GPU_LOAD_RESET = 7,
 };
 
 /**
diff --git a/libs/binder/UtilsHost.h b/libs/binder/UtilsHost.h
index b582f17..d6fe9fa 100644
--- a/libs/binder/UtilsHost.h
+++ b/libs/binder/UtilsHost.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <functional>
 #include <optional>
 #include <ostream>
 #include <string>
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..864ff50 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*
@@ -57,7 +61,7 @@
         if (__builtin_available(android __ANDROID_API_V__, *)) {
             return APersistableBundle_readFromParcel(parcel, &mPBundle);
         } else {
-            return STATUS_FAILED_TRANSACTION;
+            return STATUS_INVALID_OPERATION;
         }
     }
 
@@ -68,7 +72,7 @@
         if (__builtin_available(android __ANDROID_API_V__, *)) {
             return APersistableBundle_writeToParcel(mPBundle, parcel);
         } else {
-            return STATUS_FAILED_TRANSACTION;
+            return STATUS_INVALID_OPERATION;
         }
     }
 
@@ -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(),
-                                       vec);
+        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(),
-                                       vec);
+        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(),
-                                      vec);
+        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/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
index eff8104..98c0cb2 100644
--- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -13,7 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+#pragma once
+
 #include <android/binder_parcel.h>
+#include <stdbool.h>
+#include <stdint.h>
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
@@ -31,14 +36,30 @@
 struct APersistableBundle;
 typedef struct APersistableBundle APersistableBundle;
 
+enum {
+    /**
+     * This can be returned from functions that need to distinguish between an empty
+     * value and a non-existent key.
+     */
+    APERSISTABLEBUNDLE_KEY_NOT_FOUND = -1,
+
+    /**
+     * This can be returned from functions that take a APersistableBundle_stringAllocator.
+     * This means the allocator has failed and returned a nullptr.
+     */
+    APERSISTABLEBUNDLE_ALLOCATOR_FAILED = -2,
+};
+
 /**
  * This is a user supplied allocator that allocates a buffer for the
- * APersistableBundle APIs to fill in with a string.
+ * APersistableBundle APIs to fill in with a UTF-8 string.
+ * The caller that supplies this function is responsible for freeing the
+ * returned data.
  *
  * \param the required size in bytes for the allocated buffer
- * \param  void* _Nullable context if needed by the callback
+ * \param context pointer if needed by the callback
  *
- * \return allocated buffer of sizeBytes. Null if allocation failed.
+ * \return allocated buffer of sizeBytes for a UTF-8 string. Null if allocation failed.
  */
 typedef char* _Nullable (*_Nonnull APersistableBundle_stringAllocator)(int32_t sizeBytes,
                                                                        void* _Nullable context);
@@ -54,10 +75,12 @@
 
 /**
  * Create a new APersistableBundle based off an existing APersistableBundle.
+ * This is a deep copy, so the new APersistableBundle has its own values from
+ * copying the original underlying PersistableBundle.
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to duplicate
+ * \param pBundle to duplicate
  *
  * \return Pointer to a new APersistableBundle
  */
@@ -68,11 +91,11 @@
  * Delete an APersistableBundle. This must always be called when finished using
  * the object.
  *
- * \param bundle to delete
+ * \param pBundle to delete. No-op if null.
  *
  * Available since API level __ANDROID_API_V__.
  */
-void APersistableBundle_delete(APersistableBundle* _Nonnull pBundle)
+void APersistableBundle_delete(APersistableBundle* _Nullable pBundle)
         __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
@@ -80,8 +103,8 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param lhs bundle to compare agains the other param
- * \param rhs bundle to compare agains the other param
+ * \param lhs bundle to compare against the other param
+ * \param rhs bundle to compare against the other param
  *
  * \return true when equal, false when not
  */
@@ -134,11 +157,11 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to get the size of (number of mappings)
+ * \param pBundle to get the size of (number of mappings)
  *
  * \return number of mappings in the object
  */
-int32_t APersistableBundle_size(APersistableBundle* _Nonnull pBundle)
+int32_t APersistableBundle_size(const APersistableBundle* _Nonnull pBundle)
         __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
@@ -146,8 +169,8 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to operate on
- * \param key for the mapping to erase
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8 to erase
  *
  * \return number of entries erased. Either 0 or 1.
  */
@@ -158,8 +181,8 @@
  * Put a boolean associated with the provided key.
  * New values with the same key will overwrite existing values.
  *
- * \param bundle to operate on
- * \param key for the mapping
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
  * \param value to put for the mapping
  *
  * Available since API level __ANDROID_API_V__.
@@ -171,9 +194,9 @@
  * Put an int32_t associated with the provided key.
  * New values with the same key will overwrite existing values.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -184,9 +207,9 @@
  * Put an int64_t associated with the provided key.
  * New values with the same key will overwrite existing values.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -197,9 +220,9 @@
  * Put a double associated with the provided key.
  * New values with the same key will overwrite existing values.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -209,10 +232,11 @@
 /**
  * Put a string associated with the provided key.
  * New values with the same key will overwrite existing values.
+ * The value is copied.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -222,11 +246,12 @@
 /**
  * Put a boolean vector associated with the provided key.
  * New values with the same key will overwrite existing values.
+ * The values are copied.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
- * \param size in number of elements in the vector
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -237,11 +262,12 @@
 /**
  * Put an int32_t vector associated with the provided key.
  * New values with the same key will overwrite existing values.
+ * The values are copied.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
- * \param size in number of elements in the vector
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -252,11 +278,12 @@
 /**
  * Put an int64_t vector associated with the provided key.
  * New values with the same key will overwrite existing values.
+ * The values are copied.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
- * \param size in number of elements in the vector
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -267,11 +294,12 @@
 /**
  * Put a double vector associated with the provided key.
  * New values with the same key will overwrite existing values.
+ * The values are copied.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
- * \param size in number of elements in the vector
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -282,11 +310,12 @@
 /**
  * Put a string vector associated with the provided key.
  * New values with the same key will overwrite existing values.
+ * The values are copied.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
- * \param size in number of elements in the vector
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -298,10 +327,11 @@
 /**
  * Put an APersistableBundle associated with the provided key.
  * New values with the same key will overwrite existing values.
+ * The value is deep-copied.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param value to put for the mapping
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
  *
  * Available since API level __ANDROID_API_V__.
  */
@@ -315,9 +345,9 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to write the value to
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
  *
  * \return true if a value exists for the provided key
  */
@@ -330,9 +360,9 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to write the value to
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
  *
  * \return true if a value exists for the provided key
  */
@@ -344,9 +374,9 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to write the value to
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
  *
  * \return true if a value exists for the provided key
  */
@@ -359,9 +389,9 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to write the value to
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
  *
  * \return true if a value exists for the provided key
  */
@@ -371,17 +401,19 @@
 
 /**
  * Get a string associated with the provided key.
+ * The caller is responsible for freeing the returned data.
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to write the value to
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to in UTF-8
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
- * \return size of string associated with the provided key on success
- *         0 if no string exists for the provided key
- *         -1 if the provided allocator fails and returns false
+ * \return size of string in bytes associated with the provided key on success
+ *         APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle,
                                      const char* _Nonnull key, char* _Nullable* _Nonnull val,
@@ -393,7 +425,7 @@
  * provided pre-allocated buffer from the user.
  *
  * This function returns the size in bytes of stored vector.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -401,13 +433,14 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
  *
  * \return size of the stored vector in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
  */
 int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle,
                                             const char* _Nonnull key, bool* _Nullable buffer,
@@ -419,7 +452,7 @@
  * provided pre-allocated buffer from the user.
  *
  * This function returns the size in bytes of stored vector.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -427,13 +460,14 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
  *
  * \return size of the stored vector in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
  */
 int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle,
                                         const char* _Nonnull key, int32_t* _Nullable buffer,
@@ -444,7 +478,7 @@
  * provided pre-allocated buffer from the user.
  *
  * This function returns the size in bytes of stored vector.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -452,13 +486,14 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
  *
  * \return size of the stored vector in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
  */
 int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle,
                                          const char* _Nonnull key, int64_t* _Nullable buffer,
@@ -470,7 +505,7 @@
  * provided pre-allocated buffer from the user.
  *
  * This function returns the size in bytes of stored vector.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -478,13 +513,14 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
  *
  * \return size of the stored vector in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
  */
 int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle,
                                            const char* _Nonnull key, double* _Nullable buffer,
@@ -496,9 +532,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes of stored vector.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -506,17 +543,18 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the string pointers to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the stored vector in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
  *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getStringVector(const APersistableBundle* _Nonnull pBundle,
                                            const char* _Nonnull key,
@@ -531,9 +569,9 @@
  *
  * Available since API level __ANDROID_API_V__.
  *
- * \param bundle to operate on
- * \param key for the mapping
- * \param nonnull pointer to an APersistableBundle pointer to write to point to
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to an APersistableBundle pointer to write to point to
  * a new copy of the stored APersistableBundle. The caller takes ownership of
  * the new APersistableBundle and must be deleted with
  * APersistableBundle_delete.
@@ -550,9 +588,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -560,16 +599,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* _Nonnull pBundle,
                                           char* _Nullable* _Nullable outKeys,
@@ -583,9 +621,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -593,16 +632,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle,
                                       char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
@@ -614,9 +652,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -624,16 +663,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle,
                                        char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
@@ -645,9 +683,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -655,16 +694,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* _Nonnull pBundle,
                                          char* _Nullable* _Nullable outKeys,
@@ -678,9 +716,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -688,16 +727,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getStringKeys(const APersistableBundle* _Nonnull pBundle,
                                          char* _Nullable* _Nullable outKeys,
@@ -711,9 +749,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -721,16 +760,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* _Nonnull pBundle,
                                                 char* _Nullable* _Nullable outKeys,
@@ -744,9 +782,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -754,16 +793,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* _Nonnull pBundle,
                                             char* _Nullable* _Nullable outKeys,
@@ -777,9 +815,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -787,16 +826,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* _Nonnull pBundle,
                                              char* _Nullable* _Nullable outKeys,
@@ -810,9 +848,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -820,16 +859,14 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
  */
 int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* _Nonnull pBundle,
                                                char* _Nullable* _Nullable outKeys,
@@ -843,9 +880,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -853,15 +891,14 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
  *         false
  */
 int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* _Nonnull pBundle,
@@ -876,9 +913,10 @@
  * provided pre-allocated buffer from the user. The user must provide an
  * APersistableBundle_stringAllocator for the individual strings to be
  * allocated.
+ * The caller is responsible for freeing the returned data in bytes.
  *
  * This function returns the size in bytes required to fit the fill list of keys.
- * The supplied buffer will be filled in based on the smaller of the suplied
+ * The supplied buffer will be filled in based on the smaller of the supplied
  * bufferSizeBytes or the actual size of the stored data.
  * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
  * actual stored data, then not all of the stored data will be returned.
@@ -886,16 +924,15 @@
  * Users can call this function with null buffer and 0 bufferSizeBytes to get
  * the required size of the buffer to use on a subsequent call.
  *
- * \param bundle to operate on
- * \param nonnull pointer to a pre-allocated buffer to write the values to
- * \param size of the pre-allocated buffer
- * \param function pointer to the string dup allocator
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
  *
  * \return size of the buffer of keys in bytes. This is the required size of the
  * pre-allocated user supplied buffer if all of the stored contents are desired.
- *         0 if no string vector exists for the provided key
- *         -1 if the user supplied APersistableBundle_stringAllocator returns
- *         false
+ *         APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
  */
 int32_t APersistableBundle_getPersistableBundleKeys(
         const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys,
diff --git a/libs/binder/ndk/persistable_bundle.cpp b/libs/binder/ndk/persistable_bundle.cpp
index 404611c..9b6877d 100644
--- a/libs/binder/ndk/persistable_bundle.cpp
+++ b/libs/binder/ndk/persistable_bundle.cpp
@@ -76,7 +76,7 @@
     return pBundle->mPBundle.writeToParcel(AParcel_viewPlatformParcel(parcel));
 }
 
-int32_t APersistableBundle_size(APersistableBundle* pBundle) {
+int32_t APersistableBundle_size(const APersistableBundle* pBundle) {
     size_t size = pBundle->mPBundle.size();
     LOG_ALWAYS_FATAL_IF(size > INT32_MAX,
                         "The APersistableBundle has gotten too large! There will be an overflow in "
@@ -167,40 +167,42 @@
                                      void* context) {
     android::String16 outVal;
     bool ret = pBundle->mPBundle.getString(android::String16(key), &outVal);
-    if (ret) {
-        android::String8 tmp8(outVal);
-        *val = stringAllocator(tmp8.bytes() + 1, context);
-        if (*val) {
-            strncpy(*val, tmp8.c_str(), tmp8.bytes() + 1);
-            return tmp8.bytes();
-        } else {
-            return -1;
-        }
+    if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
+    android::String8 tmp8(outVal);
+    *val = stringAllocator(tmp8.bytes() + 1, context);
+    if (*val) {
+        strncpy(*val, tmp8.c_str(), tmp8.bytes() + 1);
+        return tmp8.bytes();
+    } else {
+        return APERSISTABLEBUNDLE_ALLOCATOR_FAILED;
     }
-    return 0;
 }
 int32_t APersistableBundle_getBooleanVector(const APersistableBundle* pBundle, const char* key,
                                             bool* buffer, int32_t bufferSizeBytes) {
     std::vector<bool> newVec;
-    pBundle->mPBundle.getBooleanVector(android::String16(key), &newVec);
+    bool ret = pBundle->mPBundle.getBooleanVector(android::String16(key), &newVec);
+    if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
     return getVecInternal<bool>(newVec, buffer, bufferSizeBytes);
 }
 int32_t APersistableBundle_getIntVector(const APersistableBundle* pBundle, const char* key,
                                         int32_t* buffer, int32_t bufferSizeBytes) {
     std::vector<int32_t> newVec;
-    pBundle->mPBundle.getIntVector(android::String16(key), &newVec);
+    bool ret = pBundle->mPBundle.getIntVector(android::String16(key), &newVec);
+    if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
     return getVecInternal<int32_t>(newVec, buffer, bufferSizeBytes);
 }
 int32_t APersistableBundle_getLongVector(const APersistableBundle* pBundle, const char* key,
                                          int64_t* buffer, int32_t bufferSizeBytes) {
     std::vector<int64_t> newVec;
-    pBundle->mPBundle.getLongVector(android::String16(key), &newVec);
+    bool ret = pBundle->mPBundle.getLongVector(android::String16(key), &newVec);
+    if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
     return getVecInternal<int64_t>(newVec, buffer, bufferSizeBytes);
 }
 int32_t APersistableBundle_getDoubleVector(const APersistableBundle* pBundle, const char* key,
                                            double* buffer, int32_t bufferSizeBytes) {
     std::vector<double> newVec;
-    pBundle->mPBundle.getDoubleVector(android::String16(key), &newVec);
+    bool ret = pBundle->mPBundle.getDoubleVector(android::String16(key), &newVec);
+    if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
     return getVecInternal<double>(newVec, buffer, bufferSizeBytes);
 }
 int32_t APersistableBundle_getStringVector(const APersistableBundle* pBundle, const char* key,
@@ -208,7 +210,8 @@
                                            APersistableBundle_stringAllocator stringAllocator,
                                            void* context) {
     std::vector<android::String16> newVec;
-    pBundle->mPBundle.getStringVector(android::String16(key), &newVec);
+    bool ret = pBundle->mPBundle.getStringVector(android::String16(key), &newVec);
+    if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
     return getStringsInternal<std::vector<android::String16>>(newVec, vec, bufferSizeBytes,
                                                               stringAllocator, context);
 }
diff --git a/libs/binder/ndk/persistable_bundle_internal.h b/libs/binder/ndk/persistable_bundle_internal.h
index 279c66f..bee10fd 100644
--- a/libs/binder/ndk/persistable_bundle_internal.h
+++ b/libs/binder/ndk/persistable_bundle_internal.h
@@ -61,7 +61,7 @@
     int32_t numAvailable = bufferSizeBytes / sizeof(char*);
     int32_t numFill = numAvailable < num ? numAvailable : num;
     if (!stringAllocator) {
-        return -1;
+        return APERSISTABLEBUNDLE_ALLOCATOR_FAILED;
     }
 
     if (numFill > 0 && buffer) {
@@ -70,7 +70,7 @@
             android::String8 tmp8 = android::String8(val);
             buffer[i] = stringAllocator(tmp8.bytes() + 1, context);
             if (buffer[i] == nullptr) {
-                return -1;
+                return APERSISTABLEBUNDLE_ALLOCATOR_FAILED;
             }
             strncpy(buffer[i], tmp8.c_str(), tmp8.bytes() + 1);
             i++;
diff --git a/libs/binder/ndk/stability.cpp b/libs/binder/ndk/stability.cpp
index 73eb863..ca3d5e6 100644
--- a/libs/binder/ndk/stability.cpp
+++ b/libs/binder/ndk/stability.cpp
@@ -27,7 +27,7 @@
 #error libbinder_ndk should only be built in a system context
 #endif
 
-#ifdef __ANDROID_VENDOR__
+#if defined(__ANDROID_VENDOR__) && !defined(__TRUSTY__)
 #error libbinder_ndk should only be built in a system context
 #endif
 
diff --git a/libs/binder/tests/binderThroughputTest.cpp b/libs/binder/tests/binderThroughputTest.cpp
index 0ea4a3f..10912c7 100644
--- a/libs/binder/tests/binderThroughputTest.cpp
+++ b/libs/binder/tests/binderThroughputTest.cpp
@@ -49,6 +49,63 @@
     }
 };
 
+static uint64_t warn_latency = std::numeric_limits<uint64_t>::max();
+
+struct ProcResults {
+    vector<uint64_t> data;
+
+    ProcResults(size_t capacity) { data.reserve(capacity); }
+
+    void add_time(uint64_t time) { data.push_back(time); }
+    void combine_with(const ProcResults& append) {
+        data.insert(data.end(), append.data.begin(), append.data.end());
+    }
+    uint64_t worst() {
+        return *max_element(data.begin(), data.end());
+    }
+    void dump() {
+        if (data.size() == 0) {
+            // This avoids index-out-of-bounds below.
+            cout << "error: no data\n" << endl;
+            return;
+        }
+
+        size_t num_long_transactions = 0;
+        for (uint64_t elem : data) {
+            if (elem > warn_latency) {
+                num_long_transactions += 1;
+            }
+        }
+
+        if (num_long_transactions > 0) {
+            cout << (double)num_long_transactions / data.size() << "% of transactions took longer "
+                "than estimated max latency. Consider setting -m to be higher than "
+                << worst() / 1000 << " microseconds" << endl;
+        }
+
+        sort(data.begin(), data.end());
+
+        uint64_t total_time = 0;
+        for (uint64_t elem : data) {
+            total_time += elem;
+        }
+
+        double best = (double)data[0] / 1.0E6;
+        double worst = (double)data.back() / 1.0E6;
+        double average = (double)total_time / data.size() / 1.0E6;
+        cout << "average:" << average << "ms worst:" << worst << "ms best:" << best << "ms" << endl;
+
+        double percentile_50 = data[(50 * data.size()) / 100] / 1.0E6;
+        double percentile_90 = data[(90 * data.size()) / 100] / 1.0E6;
+        double percentile_95 = data[(95 * data.size()) / 100] / 1.0E6;
+        double percentile_99 = data[(99 * data.size()) / 100] / 1.0E6;
+        cout << "50%: " << percentile_50 << " ";
+        cout << "90%: " << percentile_90 << " ";
+        cout << "95%: " << percentile_95 << " ";
+        cout << "99%: " << percentile_99 << endl;
+    }
+};
+
 class Pipe {
     int m_readFd;
     int m_writeFd;
@@ -79,13 +136,37 @@
         int error = read(m_readFd, &val, sizeof(val));
         ASSERT_TRUE(error >= 0);
     }
-    template <typename T> void send(const T& v) {
-        int error = write(m_writeFd, &v, sizeof(T));
+    void send(const ProcResults& v) {
+        size_t num_elems = v.data.size();
+
+        int error = write(m_writeFd, &num_elems, sizeof(size_t));
         ASSERT_TRUE(error >= 0);
+
+        char* to_write = (char*)v.data.data();
+        size_t num_bytes = sizeof(uint64_t) * num_elems;
+
+        while (num_bytes > 0) {
+            int ret = write(m_writeFd, to_write, num_bytes);
+            ASSERT_TRUE(ret >= 0);
+            num_bytes -= ret;
+            to_write += ret;
+        }
     }
-    template <typename T> void recv(T& v) {
-        int error = read(m_readFd, &v, sizeof(T));
+    void recv(ProcResults& v) {
+        size_t num_elems = 0;
+        int error = read(m_readFd, &num_elems, sizeof(size_t));
         ASSERT_TRUE(error >= 0);
+
+        v.data.resize(num_elems);
+        char* read_to = (char*)v.data.data();
+        size_t num_bytes = sizeof(uint64_t) * num_elems;
+
+        while (num_bytes > 0) {
+            int ret = read(m_readFd, read_to, num_bytes);
+            ASSERT_TRUE(ret >= 0);
+            num_bytes -= ret;
+            read_to += ret;
+        }
     }
     static tuple<Pipe, Pipe> createPipePair() {
         int a[2];
@@ -100,74 +181,6 @@
     }
 };
 
-static const uint32_t num_buckets = 128;
-static uint64_t max_time_bucket = 50ull * 1000000;
-static uint64_t time_per_bucket = max_time_bucket / num_buckets;
-
-struct ProcResults {
-    uint64_t m_worst = 0;
-    uint32_t m_buckets[num_buckets] = {0};
-    uint64_t m_transactions = 0;
-    uint64_t m_long_transactions = 0;
-    uint64_t m_total_time = 0;
-    uint64_t m_best = max_time_bucket;
-
-    void add_time(uint64_t time) {
-        if (time > max_time_bucket) {
-            m_long_transactions++;
-        }
-        m_buckets[min((uint32_t)(time / time_per_bucket), num_buckets - 1)] += 1;
-        m_best = min(time, m_best);
-        m_worst = max(time, m_worst);
-        m_transactions += 1;
-        m_total_time += time;
-    }
-    static ProcResults combine(const ProcResults& a, const ProcResults& b) {
-        ProcResults ret;
-        for (int i = 0; i < num_buckets; i++) {
-            ret.m_buckets[i] = a.m_buckets[i] + b.m_buckets[i];
-        }
-        ret.m_worst = max(a.m_worst, b.m_worst);
-        ret.m_best = min(a.m_best, b.m_best);
-        ret.m_transactions = a.m_transactions + b.m_transactions;
-        ret.m_long_transactions = a.m_long_transactions + b.m_long_transactions;
-        ret.m_total_time = a.m_total_time + b.m_total_time;
-        return ret;
-    }
-    void dump() {
-        if (m_long_transactions > 0) {
-            cout << (double)m_long_transactions / m_transactions << "% of transactions took longer "
-                "than estimated max latency. Consider setting -m to be higher than "
-                 << m_worst / 1000 << " microseconds" << endl;
-        }
-
-        double best = (double)m_best / 1.0E6;
-        double worst = (double)m_worst / 1.0E6;
-        double average = (double)m_total_time / m_transactions / 1.0E6;
-        cout << "average:" << average << "ms worst:" << worst << "ms best:" << best << "ms" << endl;
-
-        uint64_t cur_total = 0;
-        float time_per_bucket_ms = time_per_bucket / 1.0E6;
-        for (int i = 0; i < num_buckets; i++) {
-            float cur_time = time_per_bucket_ms * i + 0.5f * time_per_bucket_ms;
-            if ((cur_total < 0.5f * m_transactions) && (cur_total + m_buckets[i] >= 0.5f * m_transactions)) {
-                cout << "50%: " << cur_time << " ";
-            }
-            if ((cur_total < 0.9f * m_transactions) && (cur_total + m_buckets[i] >= 0.9f * m_transactions)) {
-                cout << "90%: " << cur_time << " ";
-            }
-            if ((cur_total < 0.95f * m_transactions) && (cur_total + m_buckets[i] >= 0.95f * m_transactions)) {
-                cout << "95%: " << cur_time << " ";
-            }
-            if ((cur_total < 0.99f * m_transactions) && (cur_total + m_buckets[i] >= 0.99f * m_transactions)) {
-                cout << "99%: " << cur_time << " ";
-            }
-            cur_total += m_buckets[i];
-        }
-        cout << endl;
-    }
-};
-
 String16 generateServiceName(int num)
 {
     char num_str[32];
@@ -204,34 +217,37 @@
     for (int i = 0; i < server_count; i++) {
         if (num == i)
             continue;
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-        workers.push_back(serviceMgr->getService(generateServiceName(i)));
-#pragma clang diagnostic pop
+        workers.push_back(serviceMgr->waitForService(generateServiceName(i)));
     }
 
-    // Run the benchmark if client
-    ProcResults results;
+    p.signal();
+    p.wait();
+
+    ProcResults results(iterations);
     chrono::time_point<chrono::high_resolution_clock> start, end;
-    for (int i = 0; (!cs_pair || num >= server_count) && i < iterations; i++) {
-        Parcel data, reply;
-        int target = cs_pair ? num % server_count : rand() % workers.size();
-        int sz = payload_size;
 
-        while (sz >= sizeof(uint32_t)) {
-            data.writeInt32(0);
-            sz -= sizeof(uint32_t);
-        }
-        start = chrono::high_resolution_clock::now();
-        status_t ret = workers[target]->transact(BINDER_NOP, data, &reply);
-        end = chrono::high_resolution_clock::now();
+    // Skip the benchmark if server of a cs_pair.
+    if (!(cs_pair && num < server_count)) {
+        for (int i = 0; i < iterations; i++) {
+            Parcel data, reply;
+            int target = cs_pair ? num % server_count : rand() % workers.size();
+            int sz = payload_size;
 
-        uint64_t cur_time = uint64_t(chrono::duration_cast<chrono::nanoseconds>(end - start).count());
-        results.add_time(cur_time);
+            while (sz >= sizeof(uint32_t)) {
+                data.writeInt32(0);
+                sz -= sizeof(uint32_t);
+            }
+            start = chrono::high_resolution_clock::now();
+            status_t ret = workers[target]->transact(BINDER_NOP, data, &reply);
+            end = chrono::high_resolution_clock::now();
 
-        if (ret != NO_ERROR) {
-           cout << "thread " << num << " failed " << ret << "i : " << i << endl;
-           exit(EXIT_FAILURE);
+            uint64_t cur_time = uint64_t(chrono::duration_cast<chrono::nanoseconds>(end - start).count());
+            results.add_time(cur_time);
+
+            if (ret != NO_ERROR) {
+               cout << "thread " << num << " failed " << ret << "i : " << i << endl;
+               exit(EXIT_FAILURE);
+            }
         }
     }
 
@@ -289,8 +305,15 @@
         pipes.push_back(make_worker(i, iterations, workers, payload_size, cs_pair));
     }
     wait_all(pipes);
+    // All workers have now been spawned and added themselves to service
+    // manager. Signal each worker to obtain a handle to the server workers from
+    // servicemanager.
+    signal_all(pipes);
+    // Wait for each worker to finish obtaining a handle to all server workers
+    // from servicemanager.
+    wait_all(pipes);
 
-    // Run the workers and wait for completion.
+    // Run the benchmark and wait for completion.
     chrono::time_point<chrono::high_resolution_clock> start, end;
     cout << "waiting for workers to complete" << endl;
     start = chrono::high_resolution_clock::now();
@@ -305,11 +328,10 @@
     // Collect all results from the workers.
     cout << "collecting results" << endl;
     signal_all(pipes);
-    ProcResults tot_results;
+    ProcResults tot_results(0), tmp_results(0);
     for (int i = 0; i < workers; i++) {
-        ProcResults tmp_results;
         pipes[i].recv(tmp_results);
-        tot_results = ProcResults::combine(tot_results, tmp_results);
+        tot_results.combine_with(tmp_results);
     }
 
     // Kill all the workers.
@@ -323,13 +345,11 @@
         }
     }
     if (training_round) {
-        // sets max_time_bucket to 2 * m_worst from the training round.
-        // Also needs to adjust time_per_bucket accordingly.
-        max_time_bucket = 2 * tot_results.m_worst;
-        time_per_bucket = max_time_bucket / num_buckets;
-        cout << "Max latency during training: " << tot_results.m_worst / 1.0E6 << "ms" << endl;
+        // Sets warn_latency to 2 * worst from the training round.
+        warn_latency = 2 * tot_results.worst();
+        cout << "Max latency during training: " << tot_results.worst() / 1.0E6 << "ms" << endl;
     } else {
-            tot_results.dump();
+        tot_results.dump();
     }
 }
 
@@ -340,8 +360,7 @@
     int payload_size = 0;
     bool cs_pair = false;
     bool training_round = false;
-    (void)argc;
-    (void)argv;
+    int max_time_us;
 
     // Parse arguments.
     for (int i = 1; i < argc; i++) {
@@ -351,46 +370,65 @@
             cout << "\t-m N    : Specify expected max latency in microseconds." << endl;
             cout << "\t-p      : Split workers into client/server pairs." << endl;
             cout << "\t-s N    : Specify payload size." << endl;
-            cout << "\t-t N    : Run training round." << endl;
+            cout << "\t-t      : Run training round." << endl;
             cout << "\t-w N    : Specify total number of workers." << endl;
             return 0;
         }
         if (string(argv[i]) == "-w") {
+            if (i + 1 == argc) {
+                cout << "-w requires an argument\n" << endl;
+                exit(EXIT_FAILURE);
+            }
             workers = atoi(argv[i+1]);
             i++;
             continue;
         }
         if (string(argv[i]) == "-i") {
+            if (i + 1 == argc) {
+                cout << "-i requires an argument\n" << endl;
+                exit(EXIT_FAILURE);
+            }
             iterations = atoi(argv[i+1]);
             i++;
             continue;
         }
         if (string(argv[i]) == "-s") {
+            if (i + 1 == argc) {
+                cout << "-s requires an argument\n" << endl;
+                exit(EXIT_FAILURE);
+            }
             payload_size = atoi(argv[i+1]);
             i++;
+            continue;
         }
         if (string(argv[i]) == "-p") {
             // client/server pairs instead of spreading
             // requests to all workers. If true, half
             // the workers become clients and half servers
             cs_pair = true;
+            continue;
         }
         if (string(argv[i]) == "-t") {
             // Run one training round before actually collecting data
             // to get an approximation of max latency.
             training_round = true;
+            continue;
         }
         if (string(argv[i]) == "-m") {
+            if (i + 1 == argc) {
+                cout << "-m requires an argument\n" << endl;
+                exit(EXIT_FAILURE);
+            }
             // Caller specified the max latency in microseconds.
             // No need to run training round in this case.
-            if (atoi(argv[i+1]) > 0) {
-                max_time_bucket = strtoull(argv[i+1], (char **)nullptr, 10) * 1000;
-                time_per_bucket = max_time_bucket / num_buckets;
-                i++;
-            } else {
+            max_time_us = atoi(argv[i+1]);
+            if (max_time_us <= 0) {
                 cout << "Max latency -m must be positive." << endl;
                 exit(EXIT_FAILURE);
             }
+            warn_latency = max_time_us * 1000ull;
+            i++;
+            continue;
         }
     }
 
diff --git a/libs/binder/tests/format.h b/libs/binder/tests/format.h
index b5440a4..c588de7 100644
--- a/libs/binder/tests/format.h
+++ b/libs/binder/tests/format.h
@@ -18,7 +18,7 @@
 // ETA for this blocker is 2023-10-27~2023-11-10.
 // Also, remember to remove fmtlib's format.cc from trusty makefiles.
 
-#if __has_include(<format>)
+#if __has_include(<format>) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
 #include <format>
 #else
 #include <fmt/format.h>
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index 6de7eb5..d343f14 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -30,6 +30,9 @@
 	external/rust/crates/downcast-rs \
 	trusty/user/base/lib/trusty-sys \
 
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+
 # Trusty does not have `ProcessState`, so there are a few
 # doc links in `IBinder` that are still broken.
 MODULE_RUSTFLAGS += \
diff --git a/libs/fakeservicemanager/FakeServiceManager.cpp b/libs/fakeservicemanager/FakeServiceManager.cpp
index ae242f3..08f30de 100644
--- a/libs/fakeservicemanager/FakeServiceManager.cpp
+++ b/libs/fakeservicemanager/FakeServiceManager.cpp
@@ -122,9 +122,19 @@
 }
 
 void FakeServiceManager::clear() {
-    std::lock_guard<std::mutex> l(mMutex);
+    std::map<String16, sp<IBinder>> backup;
 
-    mNameToService.clear();
+    {
+      std::lock_guard<std::mutex> l(mMutex);
+      backup = mNameToService;
+      mNameToService.clear();
+    }
+
+    // destructors may access FSM, so avoid recursive lock
+    backup.clear(); // explicit
+
+    // TODO: destructors may have added more services here - may want
+    // to check this or abort
 }
 }  // namespace android
 
@@ -147,4 +157,4 @@
     LOG_ALWAYS_FATAL_IF(gFakeServiceManager == nullptr, "Fake Service Manager is not available. Forgot to call setupFakeServiceManager?");
     gFakeServiceManager->clear();
 }
-} //extern "C"
\ No newline at end of file
+} //extern "C"
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index c6f3363..7d37fd3 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -344,8 +344,11 @@
     handleRefreshRateUpdates();
 }
 
-void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId, int32_t, int32_t) {
-    LOG_ALWAYS_FATAL("dispatchHdcpLevelsChanged was called but was never registered");
+void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                              int32_t maxLevel) {
+    ALOGV("choreographer %p ~ received hdcp levels change event (displayId=%s, connectedLevel=%d, "
+          "maxLevel=%d), ignoring.",
+          this, to_string(displayId).c_str(), connectedLevel, maxLevel);
 }
 
 void Choreographer::handleMessage(const Message& message) {
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index ba1d196..95b2641 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -258,8 +258,7 @@
     mInfo = handle->mInfo;
 }
 
-std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) {
-    const WindowInfo& info = *window.getInfo();
+std::ostream& operator<<(std::ostream& out, const WindowInfo& info) {
     std::string transform;
     info.transform.dump(transform, "transform", "    ");
     out << "name=" << info.name << ", id=" << info.id << ", displayId=" << info.displayId
@@ -277,4 +276,10 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) {
+    const WindowInfo& info = *window.getInfo();
+    out << info;
+    return out;
+}
+
 } // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index b72b71a..e72fd59 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -269,6 +269,8 @@
     status_t readFromParcel(const android::Parcel* parcel) override;
 };
 
+std::ostream& operator<<(std::ostream& out, const WindowInfo& window);
+
 /*
  * Handle for a window that can receive input.
  *
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index dd8dc8d..b74c3b2 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -220,7 +220,7 @@
         "libPlatformProperties",
         "libtinyxml2",
         "libutils",
-        "libvintf",
+        "libz", // needed by libkernelconfigs
         "server_configurable_flags",
     ],
 
@@ -239,6 +239,7 @@
         "libgui_window_info_static",
         "libui-types",
         "libtflite_static",
+        "libkernelconfigs",
     ],
 
     whole_static_libs: [
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 669f801..598f949 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -10,6 +10,7 @@
 #include <fcntl.h>
 #include <inttypes.h>
 #include <math.h>
+#include <poll.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -517,6 +518,22 @@
     return OK;
 }
 
+bool InputChannel::probablyHasInput() const {
+    struct pollfd pfds = {.fd = mFd, .events = POLLIN};
+    if (::poll(&pfds, /*nfds=*/1, /*timeout=*/0) <= 0) {
+        // This can be a false negative because EAGAIN and ENOMEM are not handled. The latter should
+        // be extremely rare. The EAGAIN is also unlikely because it happens only when the signal
+        // arrives while the syscall is executed, and the syscall is quick. Hitting EAGAIN too often
+        // would be a sign of having too many signals, which is a bigger performance problem. A
+        // common tradition is to repeat the syscall on each EAGAIN, but it is not necessary here.
+        // In other words, the missing one liner is replaced by a multiline explanation.
+        return false;
+    }
+    // From poll(2): The bits returned in |revents| can include any of those specified in |events|,
+    // or one of the values POLLERR, POLLHUP, or POLLNVAL.
+    return (pfds.revents & POLLIN) != 0;
+}
+
 std::unique_ptr<InputChannel> InputChannel::dup() const {
     base::unique_fd newFd(dupFd());
     return InputChannel::create(getName(), std::move(newFd), getConnectionToken());
@@ -1406,6 +1423,10 @@
     return head.body.motion.source;
 }
 
+bool InputConsumer::probablyHasInput() const {
+    return hasPendingBatch() || mChannel->probablyHasInput();
+}
+
 ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
     for (size_t i = 0; i < mBatches.size(); i++) {
         const Batch& batch = mBatches[i];
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index 3c1ae3e..ab8c341 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -27,8 +27,7 @@
 #include <utils/Timers.h>
 #include <utils/Tokenizer.h>
 #if defined(__ANDROID__)
-#include <vintf/RuntimeInfo.h>
-#include <vintf/VintfObject.h>
+#include <vintf/KernelConfigs.h>
 #endif
 
 #include <cstdlib>
@@ -98,12 +97,10 @@
 
 bool kernelConfigsArePresent(const std::set<std::string>& configs) {
 #if defined(__ANDROID__)
-    std::shared_ptr<const android::vintf::RuntimeInfo> runtimeInfo =
-            android::vintf::VintfObject::GetInstance()->getRuntimeInfo(
-                    vintf::RuntimeInfo::FetchFlag::CONFIG_GZ);
-    LOG_ALWAYS_FATAL_IF(runtimeInfo == nullptr, "Kernel configs could not be fetched");
+    std::map<std::string, std::string> kernelConfigs;
+    const status_t result = android::kernelconfigs::LoadKernelConfigs(&kernelConfigs);
+    LOG_ALWAYS_FATAL_IF(result != OK, "Kernel configs could not be fetched");
 
-    const std::map<std::string, std::string>& kernelConfigs = runtimeInfo->kernelConfigs();
     for (const std::string& requiredConfig : configs) {
         const auto configIt = kernelConfigs.find(requiredConfig);
         if (configIt == kernelConfigs.end()) {
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 138898f..13cfb49 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -36,8 +36,10 @@
         "libgmock",
         "libgui_window_info_static",
         "libinput",
+        "libkernelconfigs",
         "libtflite_static",
         "libui-types",
+        "libz", // needed by libkernelconfigs
     ],
     cflags: [
         "-Wall",
@@ -61,7 +63,6 @@
         "libPlatformProperties",
         "libtinyxml2",
         "libutils",
-        "libvintf",
         "server_configurable_flags",
     ],
     data: [
diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp
index 0661261..650c930 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -81,8 +81,7 @@
             << "client channel should have suffixed name";
 
     // Server->Client communication
-    InputMessage serverMsg;
-    memset(&serverMsg, 0, sizeof(InputMessage));
+    InputMessage serverMsg = {};
     serverMsg.header.type = InputMessage::Type::KEY;
     serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN;
     EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg))
@@ -97,8 +96,7 @@
             << "client channel should receive the correct message from server channel";
 
     // Client->Server communication
-    InputMessage clientReply;
-    memset(&clientReply, 0, sizeof(InputMessage));
+    InputMessage clientReply = {};
     clientReply.header.type = InputMessage::Type::FINISHED;
     clientReply.header.seq = 0x11223344;
     clientReply.body.finished.handled = true;
@@ -116,6 +114,48 @@
             << "server channel should receive the correct message from client channel";
 }
 
+TEST_F(InputChannelTest, ProbablyHasInput) {
+    std::unique_ptr<InputChannel> senderChannel, receiverChannel;
+
+    // Open a pair of channels.
+    status_t result =
+            InputChannel::openInputChannelPair("channel name", senderChannel, receiverChannel);
+    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
+
+    ASSERT_FALSE(receiverChannel->probablyHasInput());
+
+    // Send one message.
+    InputMessage serverMsg = {};
+    serverMsg.header.type = InputMessage::Type::KEY;
+    serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN;
+    EXPECT_EQ(OK, senderChannel->sendMessage(&serverMsg))
+            << "server channel should be able to send message to client channel";
+
+    // Verify input is available.
+    bool hasInput = false;
+    do {
+        // The probablyHasInput() can return false positive under rare circumstances uncontrollable
+        // by the tests. Re-request the availability in this case. Returning |false| for a long
+        // time is not intended, and would cause a test timeout.
+        hasInput = receiverChannel->probablyHasInput();
+    } while (!hasInput);
+    EXPECT_TRUE(hasInput)
+            << "client channel should observe that message is available before receiving it";
+
+    // Receive (consume) the message.
+    InputMessage clientMsg;
+    EXPECT_EQ(OK, receiverChannel->receiveMessage(&clientMsg))
+            << "client channel should be able to receive message from server channel";
+    EXPECT_EQ(serverMsg.header.type, clientMsg.header.type)
+            << "client channel should receive the correct message from server channel";
+    EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action)
+            << "client channel should receive the correct message from server channel";
+
+    // Verify input is not available.
+    EXPECT_FALSE(receiverChannel->probablyHasInput())
+            << "client should not observe any more messages after receiving the single one";
+}
+
 TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) {
     std::unique_ptr<InputChannel> serverChannel, clientChannel;
 
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 06b841b..2000335 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -40,6 +40,182 @@
     bool isResampled = false;
 };
 
+// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct
+// allow to check the expectations against the event acquired from the InputReceiver. To help
+// simplify expectation checking it carries members not present in MotionEvent, like |rawXScale|.
+struct PublishMotionArgs {
+    const int32_t action;
+    const nsecs_t downTime;
+    const uint32_t seq;
+    const int32_t eventId;
+    const int32_t deviceId = 1;
+    const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
+    const int32_t displayId = ADISPLAY_ID_DEFAULT;
+    const int32_t actionButton = 0;
+    const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
+    const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
+    const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
+    const float xScale = 2;
+    const float yScale = 3;
+    const float xOffset = -10;
+    const float yOffset = -20;
+    const float rawXScale = 4;
+    const float rawYScale = -5;
+    const float rawXOffset = -11;
+    const float rawYOffset = 42;
+    const float xPrecision = 0.25;
+    const float yPrecision = 0.5;
+    const float xCursorPosition = 1.3;
+    const float yCursorPosition = 50.6;
+    std::array<uint8_t, 32> hmac;
+    int32_t flags;
+    ui::Transform transform;
+    ui::Transform rawTransform;
+    const nsecs_t eventTime;
+    size_t pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+
+    PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers,
+                      const uint32_t seq);
+};
+
+PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime,
+                                     const std::vector<Pointer>& pointers, const uint32_t inSeq)
+      : action(inAction),
+        downTime(inDownTime),
+        seq(inSeq),
+        eventId(InputEvent::nextId()),
+        eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) {
+    hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
+            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
+
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    pointerCount = pointers.size();
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back({});
+        pointerProperties[i].clear();
+        pointerProperties[i].id = pointers[i].id;
+        pointerProperties[i].toolType = ToolType::FINGER;
+
+        pointerCoords.push_back({});
+        pointerCoords[i].clear();
+        pointerCoords[i].isResampled = pointers[i].isResampled;
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
+    }
+    transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+}
+
+// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point
+// comparisons limit precision to EPSILON.
+void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) {
+    EXPECT_EQ(args.eventId, motionEvent.getId());
+    EXPECT_EQ(args.deviceId, motionEvent.getDeviceId());
+    EXPECT_EQ(args.source, motionEvent.getSource());
+    EXPECT_EQ(args.displayId, motionEvent.getDisplayId());
+    EXPECT_EQ(args.hmac, motionEvent.getHmac());
+    EXPECT_EQ(args.action, motionEvent.getAction());
+    EXPECT_EQ(args.downTime, motionEvent.getDownTime());
+    EXPECT_EQ(args.flags, motionEvent.getFlags());
+    EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags());
+    EXPECT_EQ(args.metaState, motionEvent.getMetaState());
+    EXPECT_EQ(args.buttonState, motionEvent.getButtonState());
+    EXPECT_EQ(args.classification, motionEvent.getClassification());
+    EXPECT_EQ(args.transform, motionEvent.getTransform());
+    EXPECT_EQ(args.xOffset, motionEvent.getXOffset());
+    EXPECT_EQ(args.yOffset, motionEvent.getYOffset());
+    EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision());
+    EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision());
+    EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(),
+                EPSILON);
+    EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(),
+                EPSILON);
+    EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform());
+    EXPECT_EQ(args.eventTime, motionEvent.getEventTime());
+    EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount());
+    EXPECT_EQ(0U, motionEvent.getHistorySize());
+
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i));
+        EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i));
+
+        const auto& pc = args.pointerCoords[i];
+        EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]);
+
+        EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON);
+        EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON);
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i));
+
+        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
+        // "up", and the positive y direction is "down".
+        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        const float x = sinf(unscaledOrientation) * args.xScale;
+        const float y = -cosf(unscaledOrientation) * args.yScale;
+        EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i));
+    }
+}
+
+void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) {
+    status_t status =
+            publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId,
+                                         a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags,
+                                         a.metaState, a.buttonState, a.classification, a.transform,
+                                         a.xPrecision, a.yPrecision, a.xCursorPosition,
+                                         a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime,
+                                         a.pointerCount, a.pointerProperties.data(),
+                                         a.pointerCoords.data());
+    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+}
+
+void sendAndVerifyFinishedSignal(InputConsumer& consumer, InputPublisher& publisher, uint32_t seq,
+                                 nsecs_t publishTime) {
+    status_t status = consumer.sendFinishedSignal(seq, false);
+    ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
+    Result<InputPublisher::ConsumerResponse> result = publisher.receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_FALSE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
+            << "finished signal's consume time should be greater than publish time";
+}
+
+void waitUntilInputAvailable(const InputConsumer& inputConsumer) {
+    bool hasInput;
+    do {
+        // The probablyHasInput() can return false positive under rare circumstances uncontrollable
+        // by the tests. Re-request the availability in this case. Returning |false| for a long
+        // time is not intended, and would cause a test timeout.
+        hasInput = inputConsumer.probablyHasInput();
+    } while (!hasInput);
+}
+
 } // namespace
 
 class InputPublisherAndConsumerTest : public testing::Test {
@@ -63,6 +239,8 @@
 
     void publishAndConsumeKeyEvent();
     void publishAndConsumeMotionStream();
+    void publishAndConsumeMotionDown(nsecs_t downTime);
+    void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
     void publishAndConsumeFocusEvent();
     void publishAndConsumeCaptureEvent();
     void publishAndConsumeDragEvent();
@@ -73,16 +251,6 @@
 private:
     // The sequence number to use when publishing the next event
     uint32_t mSeq = 1;
-
-    void publishAndConsumeMotionEvent(
-            int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
-            int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
-            int32_t metaState, int32_t buttonState, MotionClassification classification,
-            float xScale, float yScale, float xOffset, float yOffset, float xPrecision,
-            float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale,
-            float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime,
-            nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties,
-            const std::vector<PointerCoords>& pointerCoords);
 };
 
 TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -121,11 +289,14 @@
     ASSERT_EQ(OK, status)
             << "publisher publishKeyEvent should return OK";
 
+    waitUntilInputAvailable(*mConsumer);
     uint32_t consumeSeq;
     InputEvent* event;
     status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status)
             << "consumer consume should return OK";
+    EXPECT_FALSE(mConsumer->probablyHasInput())
+            << "no events should be waiting after being consumed";
 
     ASSERT_TRUE(event != nullptr)
             << "consumer should have returned non-NULL event";
@@ -185,176 +356,51 @@
                                   Pointer{.id = 2, .x = 300, .y = 400}});
 }
 
-void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
-        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
-    constexpr int32_t deviceId = 1;
-    constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
-    constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
-    constexpr std::array<uint8_t, 32> hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
-                                              11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
-                                              22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
-    constexpr int32_t actionButton = 0;
-    int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+void InputPublisherAndConsumerTest::publishAndConsumeMotionDown(nsecs_t downTime) {
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+}
 
-    if (action == AMOTION_EVENT_ACTION_CANCEL) {
-        flags |= AMOTION_EVENT_FLAG_CANCELED;
-    }
-    const size_t pointerCount = pointers.size();
-    constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
-    constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
-    constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
-    constexpr MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
-    constexpr float xScale = 2;
-    constexpr float yScale = 3;
-    constexpr float xOffset = -10;
-    constexpr float yOffset = -20;
-    constexpr float rawXScale = 4;
-    constexpr float rawYScale = -5;
-    constexpr float rawXOffset = -11;
-    constexpr float rawYOffset = 42;
-    constexpr float xPrecision = 0.25;
-    constexpr float yPrecision = 0.5;
-    constexpr float xCursorPosition = 1.3;
-    constexpr float yCursorPosition = 50.6;
+void InputPublisherAndConsumerTest::publishAndConsumeBatchedMotionMove(nsecs_t downTime) {
+    uint32_t seq = mSeq++;
+    const std::vector<Pointer> pointers = {Pointer{.id = 0, .x = 20, .y = 30}};
+    PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq);
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, args);
 
-    const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    std::vector<PointerProperties> pointerProperties;
-    std::vector<PointerCoords> pointerCoords;
-    for (size_t i = 0; i < pointerCount; i++) {
-        pointerProperties.push_back({});
-        pointerProperties[i].clear();
-        pointerProperties[i].id = pointers[i].id;
-        pointerProperties[i].toolType = ToolType::FINGER;
-
-        pointerCoords.push_back({});
-        pointerCoords[i].clear();
-        pointerCoords[i].isResampled = pointers[i].isResampled;
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
-    }
-
-    publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags,
-                                 edgeFlags, metaState, buttonState, classification, xScale, yScale,
-                                 xOffset, yOffset, xPrecision, yPrecision, xCursorPosition,
-                                 yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset,
-                                 downTime, eventTime, pointerProperties, pointerCoords);
+    // Consume leaving a batch behind.
+    uint32_t consumeSeq;
+    InputEvent* event;
+    status_t status = mConsumer->consume(&mEventFactory,
+                                         /*consumeBatches=*/false, -1, &consumeSeq, &event);
+    ASSERT_EQ(WOULD_BLOCK, status)
+            << "consumer consume should return WOULD_BLOCK when a new batch is started";
+    ASSERT_TRUE(mConsumer->hasPendingBatch()) << "consume should have created a batch";
+    EXPECT_TRUE(mConsumer->probablyHasInput())
+            << "should deterministically have input because there is a batch";
+    sendAndVerifyFinishedSignal(*mConsumer, *mPublisher, seq, publishTime);
 }
 
 void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
-        int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
-        int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
-        int32_t buttonState, MotionClassification classification, float xScale, float yScale,
-        float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
-        float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset,
-        nsecs_t downTime, nsecs_t eventTime,
-        const std::vector<PointerProperties>& pointerProperties,
-        const std::vector<PointerCoords>& pointerCoords) {
-    const uint32_t seq = mSeq++;
-    const int32_t eventId = InputEvent::nextId();
-    ui::Transform transform;
-    transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
-    ui::Transform rawTransform;
-    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
-
-    status_t status;
-    ASSERT_EQ(pointerProperties.size(), pointerCoords.size());
-    const size_t pointerCount = pointerProperties.size();
-    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
-                                            actionButton, flags, edgeFlags, metaState, buttonState,
-                                            classification, transform, xPrecision, yPrecision,
-                                            xCursorPosition, yCursorPosition, rawTransform,
-                                            downTime, eventTime, pointerCount,
-                                            pointerProperties.data(), pointerCoords.data());
-    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
+    uint32_t seq = mSeq++;
+    PublishMotionArgs args(action, downTime, pointers, seq);
+    nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, args);
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
-    ASSERT_EQ(OK, status)
-            << "consumer consume should return OK";
-
+    status_t status =
+            mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event);
+    ASSERT_EQ(OK, status) << "consumer consume should return OK";
     ASSERT_TRUE(event != nullptr)
             << "consumer should have returned non-NULL event";
     ASSERT_EQ(InputEventType::MOTION, event->getType())
             << "consumer should have returned a motion event";
-
-    MotionEvent* motionEvent = static_cast<MotionEvent*>(event);
     EXPECT_EQ(seq, consumeSeq);
-    EXPECT_EQ(eventId, motionEvent->getId());
-    EXPECT_EQ(deviceId, motionEvent->getDeviceId());
-    EXPECT_EQ(source, motionEvent->getSource());
-    EXPECT_EQ(displayId, motionEvent->getDisplayId());
-    EXPECT_EQ(hmac, motionEvent->getHmac());
-    EXPECT_EQ(action, motionEvent->getAction());
-    EXPECT_EQ(flags, motionEvent->getFlags());
-    EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags());
-    EXPECT_EQ(metaState, motionEvent->getMetaState());
-    EXPECT_EQ(buttonState, motionEvent->getButtonState());
-    EXPECT_EQ(classification, motionEvent->getClassification());
-    EXPECT_EQ(transform, motionEvent->getTransform());
-    EXPECT_EQ(xOffset, motionEvent->getXOffset());
-    EXPECT_EQ(yOffset, motionEvent->getYOffset());
-    EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
-    EXPECT_EQ(yPrecision, motionEvent->getYPrecision());
-    EXPECT_NEAR(xCursorPosition, motionEvent->getRawXCursorPosition(), EPSILON);
-    EXPECT_NEAR(yCursorPosition, motionEvent->getRawYCursorPosition(), EPSILON);
-    EXPECT_NEAR(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition(), EPSILON);
-    EXPECT_NEAR(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition(), EPSILON);
-    EXPECT_EQ(rawTransform, motionEvent->getRawTransform());
-    EXPECT_EQ(downTime, motionEvent->getDownTime());
-    EXPECT_EQ(eventTime, motionEvent->getEventTime());
-    EXPECT_EQ(pointerCount, motionEvent->getPointerCount());
-    EXPECT_EQ(0U, motionEvent->getHistorySize());
 
-    for (size_t i = 0; i < pointerCount; i++) {
-        SCOPED_TRACE(i);
-        EXPECT_EQ(pointerProperties[i].id, motionEvent->getPointerId(i));
-        EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i));
-
-        const auto& pc = pointerCoords[i];
-        EXPECT_EQ(pc, motionEvent->getSamplePointerCoords()[i]);
-
-        EXPECT_NEAR(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i), EPSILON);
-        EXPECT_NEAR(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i), EPSILON);
-        EXPECT_NEAR(pc.getX() * xScale + xOffset, motionEvent->getX(i), EPSILON);
-        EXPECT_NEAR(pc.getY() * yScale + yOffset, motionEvent->getY(i), EPSILON);
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent->getTouchMinor(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent->getToolMajor(i));
-        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent->getToolMinor(i));
-
-        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
-        // "up", and the positive y direction is "down".
-        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
-        const float x = sinf(unscaledOrientation) * xScale;
-        const float y = -cosf(unscaledOrientation) * yScale;
-        EXPECT_EQ(atan2f(x, -y), motionEvent->getOrientation(i));
-    }
-
-    status = mConsumer->sendFinishedSignal(seq, false);
-    ASSERT_EQ(OK, status)
-            << "consumer sendFinishedSignal should return OK";
-
-    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
-    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
-    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
-    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
-    ASSERT_EQ(seq, finish.seq)
-            << "receiveConsumerResponse should have returned the original sequence number";
-    ASSERT_FALSE(finish.handled)
-            << "receiveConsumerResponse should have set handled to consumer's reply";
-    ASSERT_GE(finish.consumeTime, publishTime)
-            << "finished signal's consume time should be greater than publish time";
+    verifyArgsEqualToEvent(args, static_cast<const MotionEvent&>(*event));
+    sendAndVerifyFinishedSignal(*mConsumer, *mPublisher, seq, publishTime);
 }
 
 void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() {
@@ -546,6 +592,15 @@
     ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
 }
 
+TEST_F(InputPublisherAndConsumerTest, PublishMotionMoveEvent_EndToEnd) {
+    // Publish a DOWN event before MOVE to pass the InputVerifier checks.
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime));
+
+    // Publish the MOVE event and check expectations.
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime));
+}
+
 TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
     ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
 }
diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
index 68ac7e0..e496c45 100644
--- a/libs/nativewindow/include/android/native_window_aidl.h
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -106,7 +106,7 @@
         if (__builtin_available(android __ANDROID_API_U__, *)) {
             return ANativeWindow_readFromParcel(parcel, &mWindow);
         } else {
-            return STATUS_FAILED_TRANSACTION;
+            return STATUS_INVALID_OPERATION;
         }
     }
 
@@ -117,7 +117,7 @@
         if (__builtin_available(android __ANDROID_API_U__, *)) {
             return ANativeWindow_writeToParcel(mWindow, parcel);
         } else {
-            return STATUS_FAILED_TRANSACTION;
+            return STATUS_INVALID_OPERATION;
         }
     }
 
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index a98ea86..969a5cf 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1095,10 +1095,19 @@
     ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL = 3,
 
     /**
+     * Indicates that, as a result of a user interaction, an animation is likely to start.
+     * This category is a signal that a user interaction heuristic determined the need of a
+     * high refresh rate, and is not an explicit request from the app.
+     * As opposed to FRAME_RATE_CATEGORY_HIGH, this vote may be ignored in favor of
+     * more explicit votes.
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT = 4,
+
+    /**
      * Indicates a frame rate suitable for animations that require a high frame rate, which may
      * increase smoothness but may also increase power usage.
      */
-    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 5
 };
 
 /*
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index a185a59..8bc1292 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -2,11 +2,11 @@
   global:
     AHardwareBuffer_acquire;
     AHardwareBuffer_allocate;
-    AHardwareBuffer_allocate2; # llndk # systemapi
-    AHardwareBuffer_createFromHandle; # llndk # systemapi
+    AHardwareBuffer_allocate2; # llndk systemapi
+    AHardwareBuffer_createFromHandle; # llndk systemapi
     AHardwareBuffer_describe;
     AHardwareBuffer_getId; # introduced=31
-    AHardwareBuffer_getNativeHandle; # llndk # systemapi
+    AHardwareBuffer_getNativeHandle; # llndk systemapi
     AHardwareBuffer_isSupported; # introduced=29
     AHardwareBuffer_lock;
     AHardwareBuffer_lockAndGetInfo; # introduced=29
@@ -17,8 +17,8 @@
     AHardwareBuffer_unlock;
     AHardwareBuffer_readFromParcel; # introduced=34
     AHardwareBuffer_writeToParcel; # introduced=34
-    AHardwareBuffer_getDataSpace; # llndk # systemapi
-    AHardwareBuffer_setDataSpace; # llndk # systemapi
+    AHardwareBuffer_getDataSpace; # llndk systemapi
+    AHardwareBuffer_setDataSpace; # llndk systemapi
     ANativeWindowBuffer_getHardwareBuffer; # llndk
     ANativeWindow_OemStorageGet; # llndk
     ANativeWindow_OemStorageSet; # llndk
@@ -29,18 +29,18 @@
     ANativeWindow_getBuffersDefaultDataSpace; # introduced=34
     ANativeWindow_getFormat;
     ANativeWindow_getHeight;
-    ANativeWindow_getLastDequeueDuration; # systemapi # introduced=30
-    ANativeWindow_getLastDequeueStartTime; # systemapi # introduced=30
-    ANativeWindow_getLastQueueDuration; # systemapi # introduced=30
+    ANativeWindow_getLastDequeueDuration; # systemapi introduced=30
+    ANativeWindow_getLastDequeueStartTime; # systemapi introduced=30
+    ANativeWindow_getLastQueueDuration; # systemapi introduced=30
     ANativeWindow_getWidth;
     ANativeWindow_lock;
     ANativeWindow_query; # llndk
     ANativeWindow_queryf; # llndk
     ANativeWindow_queueBuffer; # llndk
-    ANativeWindow_setCancelBufferInterceptor; # systemapi # introduced=30
-    ANativeWindow_setDequeueBufferInterceptor; # systemapi # introduced=30
-    ANativeWindow_setPerformInterceptor; # systemapi # introduced=30
-    ANativeWindow_setQueueBufferInterceptor; # systemapi # introduced=30
+    ANativeWindow_setCancelBufferInterceptor; # systemapi introduced=30
+    ANativeWindow_setDequeueBufferInterceptor; # systemapi introduced=30
+    ANativeWindow_setPerformInterceptor; # systemapi introduced=30
+    ANativeWindow_setQueueBufferInterceptor; # systemapi introduced=30
     ANativeWindow_release;
     ANativeWindow_setAutoPrerotation; # llndk
     ANativeWindow_setAutoRefresh; # llndk
@@ -51,7 +51,7 @@
     ANativeWindow_setBuffersGeometry;
     ANativeWindow_setBuffersTimestamp; # llndk
     ANativeWindow_setBuffersTransform;
-    ANativeWindow_setDequeueTimeout; # systemapi # introduced=30
+    ANativeWindow_setDequeueTimeout; # systemapi introduced=30
     ANativeWindow_setFrameRate; # introduced=30
     ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
     ANativeWindow_setSharedBufferMode; # llndk
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index 92fe4c0..ee95e59 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -77,7 +77,7 @@
                                                                  backendFormat,
                                                                  isOutputBuffer);
     } else {
-        LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+        LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast<unsigned>(backend));
     }
 
     mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
@@ -145,8 +145,8 @@
                              "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
                              "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
                              "fSampleCount: %u fLevelCount: %u colorType %i",
-                             msg, tex.isValid(), dataspace, tex.width(), tex.height(),
-                             tex.hasMipmaps(), tex.isProtected(),
+                             msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
+                             tex.height(), tex.hasMipmaps(), tex.isProtected(),
                              static_cast<int>(tex.textureType()), retrievedImageInfo,
                              imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
                              colorType);
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index f8ee3fc..b82a79f 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -88,49 +88,51 @@
     SensorManager* sensorManager;
     auto iterator = sPackageInstances.find(packageName);
 
+    const uid_t uid = IPCThreadState::self()->getCallingUid();
+    const int deviceId = getDeviceIdForUid(uid);
+
+    // Return the cached instance if the device association of the package has not changed.
     if (iterator != sPackageInstances.end()) {
         sensorManager = iterator->second;
-    } else {
-        String16 opPackageName = packageName;
-        const uid_t uid = IPCThreadState::self()->getCallingUid();
-
-        // It is possible that the calling code has no access to the package name.
-        // In this case we will get the packages for the calling UID and pick the
-        // first one for attributing the app op. This will work correctly for
-        // runtime permissions as for legacy apps we will toggle the app op for
-        // all packages in the UID. The caveat is that the operation may be attributed
-        // to the wrong package and stats based on app ops may be slightly off.
-        if (opPackageName.size() <= 0) {
-            sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
-            if (binder != nullptr) {
-                Vector<String16> packages;
-                interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
-                if (!packages.isEmpty()) {
-                    opPackageName = packages[0];
-                } else {
-                    ALOGE("No packages for calling UID");
-                }
-            } else {
-                ALOGE("Cannot get permission service");
-            }
+        if (sensorManager->mDeviceId == deviceId) {
+            return *sensorManager;
         }
-
-        // Check if the calling UID is observed on a virtual device. If so, provide that device's
-        // sensors by default instead of the default device's sensors.
-        const int deviceId = getDeviceIdForUid(uid);
-        sensorManager = new SensorManager(opPackageName, deviceId);
-
-        // If we had no package name, we looked it up from the UID and the sensor
-        // manager instance we created should also be mapped to the empty package
-        // name, to avoid looking up the packages for a UID and get the same result.
-        if (packageName.size() <= 0) {
-            sPackageInstances.insert(std::make_pair(String16(), sensorManager));
-        }
-
-        // Stash the per package sensor manager.
-        sPackageInstances.insert(std::make_pair(opPackageName, sensorManager));
     }
 
+    // It is possible that the calling code has no access to the package name.
+    // In this case we will get the packages for the calling UID and pick the
+    // first one for attributing the app op. This will work correctly for
+    // runtime permissions as for legacy apps we will toggle the app op for
+    // all packages in the UID. The caveat is that the operation may be attributed
+    // to the wrong package and stats based on app ops may be slightly off.
+    String16 opPackageName = packageName;
+    if (opPackageName.size() <= 0) {
+        sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
+        if (binder != nullptr) {
+            Vector<String16> packages;
+            interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
+            if (!packages.isEmpty()) {
+                opPackageName = packages[0];
+            } else {
+                ALOGE("No packages for calling UID");
+            }
+        } else {
+            ALOGE("Cannot get permission service");
+        }
+    }
+
+    sensorManager = new SensorManager(opPackageName, deviceId);
+
+    // If we had no package name, we looked it up from the UID and the sensor
+    // manager instance we created should also be mapped to the empty package
+    // name, to avoid looking up the packages for a UID and get the same result.
+    if (packageName.size() <= 0) {
+        sPackageInstances.insert(std::make_pair(String16(), sensorManager));
+    }
+
+    // Stash the per package sensor manager.
+    sPackageInstances.insert(std::make_pair(opPackageName, sensorManager));
+
     return *sensorManager;
 }
 
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 69f42bc..a7955cf 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -104,6 +104,8 @@
         "libprotobuf-cpp-lite",
         "libstatslog",
         "libutils",
+        "libstatspull",
+        "libstatssocket",
         "server_configurable_flags",
     ],
     static_libs: [
@@ -123,14 +125,6 @@
         android: {
             shared_libs: [
                 "libgui",
-                "libstatspull",
-                "libstatssocket",
-            ],
-        },
-        host: {
-            static_libs: [
-                "libstatspull",
-                "libstatssocket",
             ],
         },
     },
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 43ebc69..72c6f1a 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -44,9 +44,11 @@
     return event;
 }
 
-InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust)
+InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust,
+                         InputFilterPolicyInterface& policy)
       : mNextListener(listener),
-        mCallbacks(ndk::SharedRefBase::make<InputFilterCallbacks>(listener)) {
+        mCallbacks(ndk::SharedRefBase::make<InputFilterCallbacks>(listener, policy)),
+        mPolicy(policy) {
     LOG_ALWAYS_FATAL_IF(!rust.createInputFilter(mCallbacks, &mInputFilterRust).isOk());
     LOG_ALWAYS_FATAL_IF(!mInputFilterRust);
 }
@@ -122,6 +124,10 @@
     if (mConfig.stickyKeysEnabled != enabled) {
         mConfig.stickyKeysEnabled = enabled;
         notifyConfigurationChangedLocked();
+        if (!enabled) {
+            // When Sticky keys is disabled, send callback to clear any saved sticky state.
+            mPolicy.notifyStickyModifierStateChanged(0, 0);
+        }
     }
 }
 
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index a38fbf6..153d29d 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -19,6 +19,7 @@
 #include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
 #include <utils/Mutex.h>
 #include "InputFilterCallbacks.h"
+#include "InputFilterPolicyInterface.h"
 #include "InputListener.h"
 #include "NotifyArgs.h"
 
@@ -47,7 +48,8 @@
             aidl::com::android::server::inputflinger::InputFilterConfiguration;
     using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo;
 
-    explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust&);
+    explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust,
+                         InputFilterPolicyInterface& policy);
     ~InputFilter() override = default;
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -65,6 +67,7 @@
 private:
     InputListenerInterface& mNextListener;
     std::shared_ptr<InputFilterCallbacks> mCallbacks;
+    InputFilterPolicyInterface& mPolicy;
     std::shared_ptr<IInputFilter> mInputFilterRust;
     // Keep track of connected peripherals, so that if filters are enabled later, we can pass that
     // info to the filters
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index 8c8f5e8..a8759b7 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -29,8 +29,9 @@
                          event.scanCode, event.metaState, event.downTime);
 }
 
-InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener)
-      : mNextListener(listener) {}
+InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener,
+                                           InputFilterPolicyInterface& policy)
+      : mNextListener(listener), mPolicy(policy) {}
 
 ndk::ScopedAStatus InputFilterCallbacks::sendKeyEvent(const AidlKeyEvent& event) {
     mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
@@ -42,6 +43,7 @@
     std::scoped_lock _l(mLock);
     mStickyModifierState.modifierState = modifierState;
     mStickyModifierState.lockedModifierState = lockedModifierState;
+    mPolicy.notifyStickyModifierStateChanged(modifierState, lockedModifierState);
     ALOGI("Sticky keys modifier state changed: modifierState=%d, lockedModifierState=%d",
           modifierState, lockedModifierState);
     return ndk::ScopedAStatus::ok();
diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h
index c0a80fb..31c160a 100644
--- a/services/inputflinger/InputFilterCallbacks.h
+++ b/services/inputflinger/InputFilterCallbacks.h
@@ -20,6 +20,7 @@
 #include <android/binder_auto_utils.h>
 #include <utils/Mutex.h>
 #include <mutex>
+#include "InputFilterPolicyInterface.h"
 #include "InputListener.h"
 #include "NotifyArgs.h"
 
@@ -33,7 +34,8 @@
 
 class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks {
 public:
-    explicit InputFilterCallbacks(InputListenerInterface& listener);
+    explicit InputFilterCallbacks(InputListenerInterface& listener,
+                                  InputFilterPolicyInterface& policy);
     ~InputFilterCallbacks() override = default;
 
     uint32_t getModifierState();
@@ -41,6 +43,7 @@
 
 private:
     InputListenerInterface& mNextListener;
+    InputFilterPolicyInterface& mPolicy;
     mutable std::mutex mLock;
     struct StickyModifierState {
         uint32_t modifierState;
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 296f244..4863513 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -127,7 +127,8 @@
  */
 InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
                            InputDispatcherPolicyInterface& dispatcherPolicy,
-                           PointerChoreographerPolicyInterface& choreographerPolicy) {
+                           PointerChoreographerPolicyInterface& choreographerPolicy,
+                           InputFilterPolicyInterface& inputFilterPolicy) {
     mInputFlingerRust = createInputFlingerRust();
 
     mDispatcher = createInputDispatcher(dispatcherPolicy);
@@ -135,7 +136,8 @@
             std::make_unique<TracedInputListener>("InputDispatcher", *mDispatcher));
 
     if (ENABLE_INPUT_FILTER_RUST) {
-        mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust);
+        mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust,
+                                                     inputFilterPolicy);
         mTracingStages.emplace_back(
                 std::make_unique<TracedInputListener>("InputFilter", *mInputFilter));
     }
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index fa7db37..df944ef 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -29,6 +29,7 @@
 
 #include <InputDispatcherInterface.h>
 #include <InputDispatcherPolicyInterface.h>
+#include <InputFilterPolicyInterface.h>
 #include <PointerChoreographerPolicyInterface.h>
 #include <input/Input.h>
 #include <input/InputTransport.h>
@@ -119,7 +120,8 @@
 public:
     InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
                  InputDispatcherPolicyInterface& dispatcherPolicy,
-                 PointerChoreographerPolicyInterface& choreographerPolicy);
+                 PointerChoreographerPolicyInterface& choreographerPolicy,
+                 InputFilterPolicyInterface& inputFilterPolicy);
 
     status_t start() override;
     status_t stop() override;
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 0be4c32..4571ef4 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -222,6 +222,7 @@
     pc.setPosition(x, y);
     if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
         pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+        pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
     } else {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 8b57730..c7bacee 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -65,6 +65,8 @@
         "libprotobuf-cpp-lite",
         "libstatslog",
         "libutils",
+        "libstatspull",
+        "libstatssocket",
         "server_configurable_flags",
     ],
     static_libs: [
@@ -75,14 +77,6 @@
         android: {
             shared_libs: [
                 "libgui",
-                "libstatspull",
-                "libstatssocket",
-            ],
-        },
-        host: {
-            static_libs: [
-                "libstatspull",
-                "libstatssocket",
             ],
         },
     },
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index ab815ea..e998d91 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -763,6 +763,21 @@
     return state.hasActiveStylus();
 }
 
+Result<void> validateWindowInfosUpdate(const gui::WindowInfosUpdate& update) {
+    struct HashFunction {
+        size_t operator()(const WindowInfo& info) const { return info.id; }
+    };
+
+    std::unordered_set<WindowInfo, HashFunction> windowSet;
+    for (const WindowInfo& info : update.windowInfos) {
+        const auto [_, inserted] = windowSet.insert(info);
+        if (!inserted) {
+            return Error() << "Duplicate entry for " << info;
+        }
+    }
+    return {};
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -835,7 +850,7 @@
         // Run a dispatch loop if there are no pending commands.
         // The dispatch loop might enqueue commands to run afterwards.
         if (!haveCommandsLocked()) {
-            dispatchOnceInnerLocked(&nextWakeupTime);
+            dispatchOnceInnerLocked(/*byref*/ nextWakeupTime);
         }
 
         // Run all pending commands if there are any.
@@ -942,7 +957,7 @@
     return DEFAULT_INPUT_DISPATCHING_TIMEOUT;
 }
 
-void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) {
     nsecs_t currentTime = now();
 
     // Reset the key repeat timer whenever normal dispatch is suspended while the
@@ -966,9 +981,7 @@
     bool isAppSwitchDue;
     if (!REMOVE_APP_SWITCH_DROPS) {
         isAppSwitchDue = mAppSwitchDueTime <= currentTime;
-        if (mAppSwitchDueTime < *nextWakeupTime) {
-            *nextWakeupTime = mAppSwitchDueTime;
-        }
+        nextWakeupTime = std::min(nextWakeupTime, mAppSwitchDueTime);
     }
 
     // Ready to start a new event.
@@ -989,9 +1002,7 @@
                 if (currentTime >= mKeyRepeatState.nextRepeatTime) {
                     mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
                 } else {
-                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
-                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
-                    }
+                    nextWakeupTime = std::min(nextWakeupTime, mKeyRepeatState.nextRepeatTime);
                 }
             }
 
@@ -1157,7 +1168,7 @@
         mLastDropReason = dropReason;
 
         releasePendingEventLocked();
-        *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
+        nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
     }
 }
 
@@ -1749,7 +1760,7 @@
 }
 
 bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
-                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
+                                        DropReason* dropReason, nsecs_t& nextWakeupTime) {
     // Preprocessing.
     if (!entry->dispatchInProgress) {
         if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN &&
@@ -1800,9 +1811,7 @@
     // Handle case where the policy asked us to try again later last time.
     if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
         if (currentTime < entry->interceptKeyWakeupTime) {
-            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
-                *nextWakeupTime = entry->interceptKeyWakeupTime;
-            }
+            nextWakeupTime = std::min(nextWakeupTime, entry->interceptKeyWakeupTime);
             return false; // wait until next wakeup
         }
         entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
@@ -1882,7 +1891,7 @@
 
 void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime,
                                            const std::shared_ptr<const SensorEntry>& entry,
-                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
+                                           DropReason* dropReason, nsecs_t& nextWakeupTime) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
               "source=0x%x, sensorType=%s",
@@ -1922,7 +1931,7 @@
 
 bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime,
                                            std::shared_ptr<const MotionEntry> entry,
-                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
+                                           DropReason* dropReason, nsecs_t& nextWakeupTime) {
     ATRACE_CALL();
     // Preprocessing.
     if (!entry->dispatchInProgress) {
@@ -2167,7 +2176,7 @@
 }
 
 sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
-        nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+        nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
         InputEventInjectionResult& outInjectionResult) {
     outInjectionResult = InputEventInjectionResult::FAILED; // Default result
 
@@ -2206,7 +2215,7 @@
             ALOGW("Waiting because no window has focus but %s may eventually add a "
                   "window when it finishes starting up. Will wait for %" PRId64 "ms",
                   mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
-            *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
+            nextWakeupTime = std::min(nextWakeupTime, *mNoFocusedWindowTimeoutTime);
             outInjectionResult = InputEventInjectionResult::PENDING;
             return nullptr;
         } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
@@ -2251,7 +2260,7 @@
     // prior input events.
     if (entry.type == EventEntry::Type::KEY) {
         if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
-            *nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
+            nextWakeupTime = std::min(nextWakeupTime, *mKeyIsWaitingForEventsTimeout);
             outInjectionResult = InputEventInjectionResult::PENDING;
             return nullptr;
         }
@@ -3847,8 +3856,7 @@
               connection->getInputChannelName().c_str(), seq, toString(handled));
     }
 
-    if (connection->status == Connection::Status::BROKEN ||
-        connection->status == Connection::Status::ZOMBIE) {
+    if (connection->status != Connection::Status::NORMAL) {
         return;
     }
 
@@ -4009,7 +4017,7 @@
 
 void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
         const std::shared_ptr<Connection>& connection, const CancelationOptions& options) {
-    if (connection->status == Connection::Status::BROKEN) {
+    if (connection->status != Connection::Status::NORMAL) {
         return;
     }
 
@@ -4121,7 +4129,7 @@
 void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
         const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
         ftl::Flags<InputTarget::Flags> targetFlags) {
-    if (connection->status == Connection::Status::BROKEN) {
+    if (connection->status != Connection::Status::NORMAL) {
         return;
     }
 
@@ -6768,6 +6776,15 @@
 }
 
 void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) {
+    if (auto result = validateWindowInfosUpdate(update); !result.ok()) {
+        {
+            // acquire lock
+            std::scoped_lock _l(mLock);
+            logDispatchStateLocked();
+        }
+        LOG_ALWAYS_FATAL("Incorrect WindowInfosUpdate provided: %s",
+                         result.error().message().c_str());
+    };
     // The listener sends the windows as a flattened array. Separate the windows by display for
     // more convenient parsing.
     std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 010dbb2..3567288 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -209,7 +209,7 @@
     // This method should only be called on the input dispatcher's own thread.
     void dispatchOnce();
 
-    void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) REQUIRES(mLock);
+    void dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) REQUIRES(mLock);
 
     // Enqueues an inbound event.  Returns true if mLooper->wake() should be called.
     bool enqueueInboundEventLocked(std::unique_ptr<EventEntry> entry) REQUIRES(mLock);
@@ -435,9 +435,9 @@
     bool dispatchDeviceResetLocked(nsecs_t currentTime, const DeviceResetEntry& entry)
             REQUIRES(mLock);
     bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
-                           DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
+                           DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
     bool dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<const MotionEntry> entry,
-                              DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
+                              DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
     void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<const FocusEntry> entry)
             REQUIRES(mLock);
     void dispatchPointerCaptureChangedLocked(
@@ -449,7 +449,7 @@
     void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<const EventEntry> entry,
                              const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
     void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<const SensorEntry>& entry,
-                              DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
+                              DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
     void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<const DragEntry> entry)
             REQUIRES(mLock);
     void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry);
@@ -521,7 +521,7 @@
 
     int32_t getTargetDisplayId(const EventEntry& entry);
     sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
-            nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+            nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<InputTarget> findTouchedWindowTargetsLocked(
             nsecs_t currentTime, const MotionEntry& entry,
diff --git a/services/inputflinger/include/InputFilterPolicyInterface.h b/services/inputflinger/include/InputFilterPolicyInterface.h
new file mode 100644
index 0000000..4d39b97
--- /dev/null
+++ b/services/inputflinger/include/InputFilterPolicyInterface.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+
+/**
+ * The InputFilter policy interface.
+ *
+ * This is the interface that InputFilter uses to talk to Input Manager and other system components.
+ */
+class InputFilterPolicyInterface {
+public:
+    virtual ~InputFilterPolicyInterface() = default;
+
+    /**
+     * A callback to notify about sticky modifier state changes when Sticky keys feature is enabled.
+     *
+     * modifierState: Current sticky modifier state which will be sent with all subsequent
+     * KeyEvents. This only includes modifiers that can be 'Sticky' which includes: Meta, Ctrl,
+     * Shift, Alt and AltGr.
+     *
+     * lockedModifierState: Current locked modifier state representing modifiers that don't get
+     * cleared after non-modifier key press. This only includes modifiers that can be 'Sticky' which
+     * includes: Meta, Ctrl, Shift, Alt and AltGr.
+     *
+     * For more information {@see sticky_keys_filter.rs}
+     */
+    virtual void notifyStickyModifierStateChanged(uint32_t modifierState,
+                                                  uint32_t lockedModifierState) = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index e1806a0..f954370 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -86,6 +86,7 @@
         "liblog",
         "libPlatformProperties",
         "libstatslog",
+        "libstatspull",
         "libutils",
     ],
     static_libs: [
@@ -99,15 +100,9 @@
         "libinputreader_headers",
     ],
     target: {
-        android: {
-            shared_libs: [
-                "libstatspull",
-            ],
-        },
         host: {
             static_libs: [
                 "libbinder",
-                "libstatspull",
             ],
         },
     },
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index f7bbc51..3ca691e 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -810,14 +810,20 @@
         case EV_SYN: {
             switch (event.code) {
                 case SYN_REPORT:
-                    currentFrameDropped = false;
+                    if (currentFrameDropped) {
+                        // To recover after a SYN_DROPPED, we need to query the state of the device
+                        // to synchronize our device state with the kernel's to account for the
+                        // dropped events on receiving the next SYN_REPORT.
+                        // Note we don't drop the SYN_REPORT at this point but it is used by the
+                        // InputDevice to reset and repopulate mapper state
+                        readDeviceState();
+                        currentFrameDropped = false;
+                    }
                     break;
                 case SYN_DROPPED:
                     // When we receive SYN_DROPPED, all events in the current frame should be
-                    // dropped. We query the state of the device to synchronize our device state
-                    // with the kernel's to account for the dropped events.
+                    // dropped up to and including next SYN_REPORT
                     currentFrameDropped = true;
-                    readDeviceState();
                     break;
                 default:
                     break;
@@ -1141,6 +1147,22 @@
     return OK;
 }
 
+base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                             size_t slotCount) const {
+    std::scoped_lock _l(mLock);
+    const Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd() || !device->absBitmask.test(axis)) {
+        return base::ResultError("device problem or axis not supported", NAME_NOT_FOUND);
+    }
+    std::vector<int32_t> outValues(slotCount + 1);
+    outValues[0] = axis;
+    const size_t bufferSize = outValues.size() * sizeof(int32_t);
+    if (ioctl(device->fd, EVIOCGMTSLOTS(bufferSize), outValues.data()) != OK) {
+        return base::ErrnoError();
+    }
+    return std::move(outValues);
+}
+
 bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
                                      uint8_t* outFlags) const {
     std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index fb32f96..a41064b 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -350,6 +350,7 @@
 
         if (mDropUntilNextSync) {
             if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+                out += reset(rawEvent->when);
                 mDropUntilNextSync = false;
                 ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun.");
             } else {
@@ -359,7 +360,6 @@
         } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
             ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
             mDropUntilNextSync = true;
-            out += reset(rawEvent->when);
         } else {
             for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
                 out += mapper.process(rawEvent);
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 0bcab42..a7e0675 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -336,6 +336,10 @@
     virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
     virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
                                           int32_t* outValue) const = 0;
+    /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size
+     * (slotCount + 1). The value at the 0 index is set to queried axis. */
+    virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                               size_t slotCount) const = 0;
     virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
 
     /*
@@ -552,6 +556,8 @@
                                      int32_t locationKeyCode) const override final;
     status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
                                   int32_t* outValue) const override final;
+    base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                       size_t slotCount) const override final;
 
     bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
                                uint8_t* outFlags) const override final;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 31dcb2e..ba7234b 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -280,7 +280,7 @@
 class InputDeviceContext {
 public:
     InputDeviceContext(InputDevice& device, int32_t eventHubId);
-    ~InputDeviceContext();
+    virtual ~InputDeviceContext();
 
     inline InputReaderContext* getContext() { return mContext; }
     inline int32_t getId() { return mDeviceId; }
@@ -372,6 +372,10 @@
     inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
         return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
     }
+    inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis,
+                                                              size_t slotCount) const {
+        return mEventHub->getMtSlotValues(mId, axis, slotCount);
+    }
     inline bool markSupportedKeyCodes(const std::vector<int32_t>& keyCodes,
                                       uint8_t* outFlags) const {
         return mEventHub->markSupportedKeyCodes(mId, keyCodes, outFlags);
@@ -450,7 +454,7 @@
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mDevice.getDeviceTypeAssociation();
     }
-    inline std::optional<DisplayViewport> getAssociatedViewport() const {
+    virtual std::optional<DisplayViewport> getAssociatedViewport() const {
         return mDevice.getAssociatedViewport();
     }
     [[nodiscard]] inline std::list<NotifyArgs> cancelTouch(nsecs_t when, nsecs_t readTime) {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index f068cc8..9e7e956 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -61,6 +61,36 @@
             scanCode >= BTN_WHEEL;
 }
 
+static bool isMediaKey(int32_t keyCode) {
+    switch (keyCode) {
+        case AKEYCODE_MEDIA_PLAY:
+        case AKEYCODE_MEDIA_PAUSE:
+        case AKEYCODE_MEDIA_PLAY_PAUSE:
+        case AKEYCODE_MUTE:
+        case AKEYCODE_HEADSETHOOK:
+        case AKEYCODE_MEDIA_STOP:
+        case AKEYCODE_MEDIA_NEXT:
+        case AKEYCODE_MEDIA_PREVIOUS:
+        case AKEYCODE_MEDIA_REWIND:
+        case AKEYCODE_MEDIA_RECORD:
+        case AKEYCODE_MEDIA_FAST_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_BACKWARD:
+        case AKEYCODE_MEDIA_STEP_FORWARD:
+        case AKEYCODE_MEDIA_STEP_BACKWARD:
+        case AKEYCODE_MEDIA_AUDIO_TRACK:
+        case AKEYCODE_VOLUME_UP:
+        case AKEYCODE_VOLUME_DOWN:
+        case AKEYCODE_VOLUME_MUTE:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN:
+            return true;
+        default:
+            return false;
+    }
+}
+
 // --- KeyboardInputMapper ---
 
 KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext,
@@ -301,7 +331,8 @@
     // For internal keyboards and devices for which the default wake behavior is explicitly
     // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
     // wake key individually.
-    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault) {
+    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
+        !(mKeyboardType != AINPUT_KEYBOARD_TYPE_ALPHABETIC && isMediaKey(keyCode))) {
         policyFlags |= POLICY_FLAG_WAKE;
     }
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 2dd05f5..0c58dab 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -35,12 +35,8 @@
 MultiTouchInputMapper::~MultiTouchInputMapper() {}
 
 std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) {
-    // 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.
+    mPointerIdBits.clear();
+    mMultiTouchMotionAccumulator.reset(mDeviceContext);
     return TouchInputMapper::reset(when);
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 255f02d..6f697db 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -399,6 +399,8 @@
                     : FloatRect{0, 0, 0, 0};
         }
         mGestureConverter.setBoundsInLogicalDisplay(*boundsInLogicalDisplay);
+
+        bumpGeneration();
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
         mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index b0fc903..b3f1700 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -30,24 +30,12 @@
                                             size_t slotCount, bool usingSlotsProtocol) {
     mUsingSlotsProtocol = usingSlotsProtocol;
     mSlots = std::vector<Slot>(slotCount);
+    populateCurrentSlot(deviceContext);
+}
 
-    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);
-        }
-    }
+void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) {
+    resetSlots();
+    syncSlots(deviceContext);
 }
 
 void MultiTouchMotionAccumulator::resetSlots() {
@@ -84,54 +72,10 @@
             if (!mUsingSlotsProtocol) {
                 slot.mInUse = true;
             }
-
-            switch (rawEvent->code) {
-                case ABS_MT_POSITION_X:
-                    slot.mAbsMtPositionX = rawEvent->value;
-                    warnIfNotInUse(*rawEvent, slot);
-                    break;
-                case ABS_MT_POSITION_Y:
-                    slot.mAbsMtPositionY = rawEvent->value;
-                    warnIfNotInUse(*rawEvent, slot);
-                    break;
-                case ABS_MT_TOUCH_MAJOR:
-                    slot.mAbsMtTouchMajor = rawEvent->value;
-                    break;
-                case ABS_MT_TOUCH_MINOR:
-                    slot.mAbsMtTouchMinor = rawEvent->value;
-                    slot.mHaveAbsMtTouchMinor = true;
-                    break;
-                case ABS_MT_WIDTH_MAJOR:
-                    slot.mAbsMtWidthMajor = rawEvent->value;
-                    break;
-                case ABS_MT_WIDTH_MINOR:
-                    slot.mAbsMtWidthMinor = rawEvent->value;
-                    slot.mHaveAbsMtWidthMinor = true;
-                    break;
-                case ABS_MT_ORIENTATION:
-                    slot.mAbsMtOrientation = rawEvent->value;
-                    break;
-                case ABS_MT_TRACKING_ID:
-                    if (mUsingSlotsProtocol && rawEvent->value < 0) {
-                        // The slot is no longer in use but it retains its previous contents,
-                        // which may be reused for subsequent touches.
-                        slot.mInUse = false;
-                    } else {
-                        slot.mInUse = true;
-                        slot.mAbsMtTrackingId = rawEvent->value;
-                    }
-                    break;
-                case ABS_MT_PRESSURE:
-                    slot.mAbsMtPressure = rawEvent->value;
-                    break;
-                case ABS_MT_DISTANCE:
-                    slot.mAbsMtDistance = rawEvent->value;
-                    break;
-                case ABS_MT_TOOL_TYPE:
-                    slot.mAbsMtToolType = rawEvent->value;
-                    slot.mHaveAbsMtToolType = true;
-                    break;
+            if (rawEvent->code == ABS_MT_POSITION_X || rawEvent->code == ABS_MT_POSITION_Y) {
+                warnIfNotInUse(*rawEvent, slot);
             }
+            slot.populateAxisValue(rawEvent->code, rawEvent->value);
         }
     } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
         // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
@@ -139,6 +83,36 @@
     }
 }
 
+void MultiTouchMotionAccumulator::syncSlots(const InputDeviceContext& deviceContext) {
+    if (!mUsingSlotsProtocol) {
+        return;
+    }
+    constexpr std::array<int32_t, 11> axisCodes = {ABS_MT_POSITION_X,  ABS_MT_POSITION_Y,
+                                                   ABS_MT_TOUCH_MAJOR, ABS_MT_TOUCH_MINOR,
+                                                   ABS_MT_WIDTH_MAJOR, ABS_MT_WIDTH_MINOR,
+                                                   ABS_MT_ORIENTATION, ABS_MT_TRACKING_ID,
+                                                   ABS_MT_PRESSURE,    ABS_MT_DISTANCE,
+                                                   ABS_MT_TOOL_TYPE};
+    const size_t numSlots = mSlots.size();
+    for (int32_t axisCode : axisCodes) {
+        if (!deviceContext.hasAbsoluteAxis(axisCode)) {
+            continue;
+        }
+        const auto result = deviceContext.getMtSlotValues(axisCode, numSlots);
+        if (result.ok()) {
+            const std::vector<int32_t>& mtSlotValues = result.value();
+            for (size_t i = 1; i <= numSlots; ++i) {
+                // The returned slot values are in a 1-indexed vector of size numSlots + 1.
+                mSlots[i - 1].populateAxisValue(axisCode, mtSlotValues[i]);
+            }
+        } else {
+            ALOGE("Could not retrieve multi-touch slot value for axis=%d error=%s status=%d",
+                  axisCode, result.error().message().c_str(), result.error().code().value());
+        }
+    }
+    populateCurrentSlot(deviceContext);
+}
+
 void MultiTouchMotionAccumulator::finishSync() {
     if (!mUsingSlotsProtocol) {
         resetSlots();
@@ -160,6 +134,21 @@
                          [](const Slot& slot) { return slot.mInUse; });
 }
 
+void MultiTouchMotionAccumulator::populateCurrentSlot(
+        const android::InputDeviceContext& deviceContext) {
+    if (!mUsingSlotsProtocol) {
+        return;
+    }
+    int32_t initialSlot;
+    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
+        status == OK) {
+        mCurrentSlot = initialSlot;
+    } else {
+        ALOGE("Could not retrieve current multi-touch slot index. status=%s",
+              statusToString(status).c_str());
+    }
+}
+
 // --- MultiTouchMotionAccumulator::Slot ---
 
 ToolType MultiTouchMotionAccumulator::Slot::getToolType() const {
@@ -176,4 +165,52 @@
     return ToolType::UNKNOWN;
 }
 
+void MultiTouchMotionAccumulator::Slot::populateAxisValue(int32_t axisCode, int32_t value) {
+    switch (axisCode) {
+        case ABS_MT_POSITION_X:
+            mAbsMtPositionX = value;
+            break;
+        case ABS_MT_POSITION_Y:
+            mAbsMtPositionY = value;
+            break;
+        case ABS_MT_TOUCH_MAJOR:
+            mAbsMtTouchMajor = value;
+            break;
+        case ABS_MT_TOUCH_MINOR:
+            mAbsMtTouchMinor = value;
+            mHaveAbsMtTouchMinor = true;
+            break;
+        case ABS_MT_WIDTH_MAJOR:
+            mAbsMtWidthMajor = value;
+            break;
+        case ABS_MT_WIDTH_MINOR:
+            mAbsMtWidthMinor = value;
+            mHaveAbsMtWidthMinor = true;
+            break;
+        case ABS_MT_ORIENTATION:
+            mAbsMtOrientation = value;
+            break;
+        case ABS_MT_TRACKING_ID:
+            if (value < 0) {
+                // The slot is no longer in use but it retains its previous contents,
+                // which may be reused for subsequent touches.
+                mInUse = false;
+            } else {
+                mInUse = true;
+                mAbsMtTrackingId = value;
+            }
+            break;
+        case ABS_MT_PRESSURE:
+            mAbsMtPressure = value;
+            break;
+        case ABS_MT_DISTANCE:
+            mAbsMtDistance = value;
+            break;
+        case ABS_MT_TOOL_TYPE:
+            mAbsMtToolType = value;
+            mHaveAbsMtToolType = true;
+            break;
+    }
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 0e3e2bb..a0f2147 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -68,12 +68,14 @@
         int32_t mAbsMtToolType = 0;
 
         void clear() { *this = Slot(); }
+        void populateAxisValue(int32_t axisCode, int32_t value);
     };
 
     MultiTouchMotionAccumulator();
 
     void configure(const InputDeviceContext& deviceContext, size_t slotCount,
                    bool usingSlotsProtocol);
+    void reset(const InputDeviceContext& deviceContext);
     void process(const RawEvent* rawEvent);
     void finishSync();
 
@@ -85,12 +87,14 @@
     }
 
 private:
-    int32_t mCurrentSlot;
+    int32_t mCurrentSlot{-1};
     std::vector<Slot> mSlots;
     bool mUsingSlotsProtocol;
 
     void resetSlots();
+    void syncSlots(const InputDeviceContext& deviceContext);
     void warnIfNotInUse(const RawEvent& event, const Slot& slot);
+    void populateCurrentSlot(const android::InputDeviceContext& deviceContext);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 01e983a..19788ce 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -81,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 << "Is hovering: " << mIsHovering << "\n";
     out << "Enable Tap Timestamp: " << mWhenToEnableTapToClick << "\n";
     return out.str();
 }
@@ -89,7 +90,7 @@
     std::list<NotifyArgs> out;
     switch (mCurrentClassification) {
         case MotionClassification::TWO_FINGER_SWIPE:
-            out.push_back(endScroll(when, when));
+            out += endScroll(when, when);
             break;
         case MotionClassification::MULTI_FINGER_SWIPE:
             out += handleMultiFingerSwipeLift(when, when);
@@ -173,6 +174,8 @@
                                                    const Gesture& gesture) {
     float deltaX = gesture.details.move.dx;
     float deltaY = gesture.details.move.dy;
+    const auto [oldXCursorPosition, oldYCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
         bool wasHoverCancelled = mIsHoverCancelled;
         // Gesture will be cancelled if it started before the user started typing and
@@ -184,6 +187,7 @@
             // 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);
+            return exitHover(when, readTime, oldXCursorPosition, oldYCursorPosition);
         } else if (mIsHoverCancelled) {
             return {};
         }
@@ -204,24 +208,27 @@
         mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
 
-    const auto [xCursorPosition, yCursorPosition] =
+    std::list<NotifyArgs> out;
+    const bool down = isPointerDown(mButtonState);
+    if (!down) {
+        out += enterHover(when, readTime, oldXCursorPosition, oldYCursorPosition);
+    }
+    const auto [newXCursorPosition, newYCursorPosition] =
             mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, newXCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, newYCursorPosition);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
-    const bool down = isPointerDown(mButtonState);
     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
 
-    const int32_t action = 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)};
+    const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
+    out.push_back(makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
+                                 /*pointerCount=*/1, &coords, newXCursorPosition,
+                                 newYCursorPosition));
+    return out;
 }
 
 std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
@@ -270,16 +277,16 @@
             newButtonState |= actionButton;
             pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
                                                  actionButton, newButtonState,
-                                                 /* pointerCount= */ 1, mFingerProps.data(),
-                                                 &coords, xCursorPosition, yCursorPosition));
+                                                 /*pointerCount=*/1, &coords, xCursorPosition,
+                                                 yCursorPosition));
         }
     }
     if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
         mDownTime = when;
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), &coords, xCursorPosition,
-                                     yCursorPosition));
+                                     &coords, xCursorPosition, yCursorPosition));
     }
     out.splice(out.end(), pressEvents);
 
@@ -295,20 +302,16 @@
             newButtonState &= ~actionButton;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                          actionButton, newButtonState, /* pointerCount= */ 1,
-                                         mFingerProps.data(), &coords, xCursorPosition,
-                                         yCursorPosition));
+                                         &coords, xCursorPosition, yCursorPosition));
         }
     }
     if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                                     newButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                                     &coords, xCursorPosition, yCursorPosition));
-        // Send a HOVER_MOVE to tell the application that the mouse is hovering again.
-        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_HOVER_MOVE,
-                                     /*actionButton=*/0, newButtonState, /*pointerCount=*/1,
-                                     mFingerProps.data(), &coords, xCursorPosition,
-                                     yCursorPosition));
+                                     newButtonState, /* pointerCount= */ 1, &coords,
+                                     xCursorPosition, yCursorPosition));
+        mButtonState = newButtonState;
+        out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     }
     mButtonState = newButtonState;
     return out;
@@ -333,18 +336,18 @@
         if (mButtonState & button) {
             newButtonState &= ~button;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                                         button, newButtonState, /*pointerCount=*/1,
-                                         mFingerProps.data(), &coords, xCursorPosition,
-                                         yCursorPosition));
+                                         button, newButtonState, /*pointerCount=*/1, &coords,
+                                         xCursorPosition, yCursorPosition));
         }
     }
+    mButtonState = 0;
     if (pointerDown) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                     newButtonState, /*pointerCount=*/1, mFingerProps.data(),
-                                     &coords, xCursorPosition, yCursorPosition));
+                                     mButtonState, /*pointerCount=*/1, &coords, xCursorPosition,
+                                     yCursorPosition));
+        out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     }
-    mButtonState = 0;
     return out;
 }
 
@@ -355,6 +358,8 @@
     const auto [xCursorPosition, yCursorPosition] =
             mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+
         mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
         coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
         coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
@@ -362,8 +367,8 @@
         mDownTime = when;
         NotifyMotionArgs args =
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0,
-                               mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                               mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                               mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
+                               xCursorPosition, yCursorPosition);
         args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
         out.push_back(args);
     }
@@ -378,8 +383,8 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
+                           xCursorPosition, yCursorPosition);
     args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     out.push_back(args);
     return out;
@@ -395,7 +400,7 @@
                 // ensure consistency between touchscreen and touchpad flings), so we're just using
                 // the "start fling" gestures as a marker for the end of a two-finger scroll
                 // gesture.
-                return {endScroll(when, readTime)};
+                return endScroll(when, readTime);
             }
             break;
         case GESTURES_FLING_TAP_DOWN:
@@ -418,18 +423,21 @@
     return {};
 }
 
-NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
+std::list<NotifyArgs> GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
+    std::list<NotifyArgs> out;
     const auto [xCursorPosition, yCursorPosition] =
             mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
+                           xCursorPosition, yCursorPosition);
     args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+    out.push_back(args);
     mCurrentClassification = MotionClassification::NONE;
-    return args;
+    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+    return out;
 }
 
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when,
@@ -445,7 +453,11 @@
         // three and then put a fourth finger down), the gesture library will treat it as two
         // separate swipes with an appropriate lift event between them, so we don't have to worry
         // about the finger count changing mid-swipe.
+
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+
         mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE;
+
         mSwipeFingerCount = fingerCount;
 
         constexpr float FAKE_FINGER_SPACING = 100;
@@ -464,16 +476,14 @@
                                           fingerCount);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
         for (size_t i = 1; i < mSwipeFingerCount; i++) {
             out.push_back(makeMotionArgs(when, readTime,
                                          AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                  (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                          /* actionButton= */ 0, mButtonState,
-                                         /* pointerCount= */ i + 1, mFingerProps.data(),
-                                         mFakeFingerCoords.data(), xCursorPosition,
-                                         yCursorPosition));
+                                         /* pointerCount= */ i + 1, mFakeFingerCoords.data(),
+                                         xCursorPosition, yCursorPosition));
         }
     }
     float rotatedDeltaX = dx, rotatedDeltaY = -dy;
@@ -491,8 +501,7 @@
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
                                  mButtonState, /* pointerCount= */ mSwipeFingerCount,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
+                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
     return out;
 }
 
@@ -512,15 +521,14 @@
                                      AMOTION_EVENT_ACTION_POINTER_UP |
                                              ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ i,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
     }
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
                                  /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
+                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT, 0);
     mCurrentClassification = MotionClassification::NONE;
+    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     mSwipeFingerCount = 0;
     return out;
 }
@@ -539,6 +547,10 @@
         LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START,
                             "First pinch gesture does not have the START zoom state (%d instead).",
                             gesture.details.pinch.zoom_state);
+        std::list<NotifyArgs> out;
+
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+
         mCurrentClassification = MotionClassification::PINCH;
         mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
         mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
@@ -551,17 +563,14 @@
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
-        std::list<NotifyArgs> out;
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
         out.push_back(makeMotionArgs(when, readTime,
                                      AMOTION_EVENT_ACTION_POINTER_DOWN |
                                              1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
         return out;
     }
 
@@ -579,8 +588,8 @@
                                       xCursorPosition + mPinchFingerSeparation / 2);
     mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
     return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0,
-                           mButtonState, /*pointerCount=*/2, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)};
+                           mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data(),
+                           xCursorPosition, yCursorPosition)};
 }
 
 std::list<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) {
@@ -593,20 +602,53 @@
                                  AMOTION_EVENT_ACTION_POINTER_UP |
                                          1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
                                  /*actionButton=*/0, mButtonState, /*pointerCount=*/2,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
-    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                 mButtonState, /*pointerCount=*/1, mFingerProps.data(),
                                  mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
-    mCurrentClassification = MotionClassification::NONE;
+    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
+                                 mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data(),
+                                 xCursorPosition, yCursorPosition));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
+    mCurrentClassification = MotionClassification::NONE;
+    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     return out;
 }
 
+std::list<NotifyArgs> GestureConverter::enterHover(nsecs_t when, nsecs_t readTime,
+                                                   float xCursorPosition, float yCursorPosition) {
+    if (!mIsHovering) {
+        mIsHovering = true;
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER, xCursorPosition,
+                               yCursorPosition)};
+    } else {
+        return {};
+    }
+}
+
+std::list<NotifyArgs> GestureConverter::exitHover(nsecs_t when, nsecs_t readTime,
+                                                  float xCursorPosition, float yCursorPosition) {
+    if (mIsHovering) {
+        mIsHovering = false;
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT, xCursorPosition,
+                               yCursorPosition)};
+    } else {
+        return {};
+    }
+}
+
+NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action,
+                                                  float xCursorPosition, float yCursorPosition) {
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+    return makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
+                          /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition);
+}
+
 NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   int32_t actionButton, int32_t buttonState,
                                                   uint32_t pointerCount,
-                                                  const PointerProperties* pointerProperties,
                                                   const PointerCoords* pointerCoords,
                                                   float xCursorPosition, float yCursorPosition) {
     return {mReaderContext.getNextId(),
@@ -624,7 +666,7 @@
             mCurrentClassification,
             AMOTION_EVENT_EDGE_FLAG_NONE,
             pointerCount,
-            pointerProperties,
+            mFingerProps.data(),
             pointerCoords,
             /* xPrecision= */ 1.0f,
             /* yPrecision= */ 1.0f,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 88e7b99..07cc56c 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -75,7 +75,7 @@
     [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime,
                                                     nsecs_t gestureStartTime,
                                                     const Gesture& gesture);
-    [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> endScroll(nsecs_t when, nsecs_t readTime);
 
     [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
                                                                uint32_t fingerCount, float dx,
@@ -85,12 +85,18 @@
                                                     const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> endPinch(nsecs_t when, nsecs_t readTime);
 
+    [[nodiscard]] std::list<NotifyArgs> enterHover(nsecs_t when, nsecs_t readTime,
+                                                   float xCursorPosition, float yCursorPosition);
+    [[nodiscard]] std::list<NotifyArgs> exitHover(nsecs_t when, nsecs_t readTime,
+                                                  float xCursorPosition, float yCursorPosition);
+
+    NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action,
+                                    float xCursorPosition, float yCursorPosition);
+
     NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                     int32_t actionButton, int32_t buttonState,
-                                    uint32_t pointerCount,
-                                    const PointerProperties* pointerProperties,
-                                    const PointerCoords* pointerCoords, float xCursorPosition,
-                                    float yCursorPosition);
+                                    uint32_t pointerCount, const PointerCoords* pointerCoords,
+                                    float xCursorPosition, float yCursorPosition);
 
     void enableTapToClick(nsecs_t when);
     bool mIsHoverCancelled{false};
@@ -111,6 +117,9 @@
     // button values (AMOTION_EVENT_BUTTON_...).
     uint32_t mButtonState = 0;
     nsecs_t mDownTime = 0;
+    // Whether we are currently in a hover state (i.e. a HOVER_ENTER event has been sent without a
+    // matching HOVER_EXIT).
+    bool mIsHovering = false;
 
     MotionClassification mCurrentClassification = MotionClassification::NONE;
     // Only used when mCurrentClassification is MULTI_FINGER_SWIPE.
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index db31ded..55aa226 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -68,6 +68,7 @@
         "TimerProvider_test.cpp",
         "TestInputListener.cpp",
         "TouchpadInputMapper_test.cpp",
+        "MultiTouchInputMapper_test.cpp",
         "KeyboardInputMapper_test.cpp",
         "UinputDevice.cpp",
         "UnwantedInteractionBlocker_test.cpp",
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 6d6b7d8..66c3256 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -16,14 +16,26 @@
 
 #include "CursorInputMapper.h"
 
+#include <list>
+#include <string>
+#include <tuple>
+#include <variant>
+
 #include <android-base/logging.h>
 #include <com_android_input_flags.h>
 #include <gtest/gtest.h>
+#include <input/DisplayViewport.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/Timers.h>
 
 #include "FakePointerController.h"
 #include "InputMapperTest.h"
+#include "InputReaderBase.h"
 #include "InterfaceMocks.h"
+#include "NotifyArgs.h"
 #include "TestEventMatchers.h"
+#include "ui/Rotation.h"
 
 #define TAG "CursorInputMapper_test"
 
@@ -40,23 +52,95 @@
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
 constexpr int32_t DISPLAY_ID = 0;
+constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1;
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
-constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+
+constexpr int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+namespace {
+
+DisplayViewport createPrimaryViewport(ui::Rotation orientation) {
+    const bool isRotated =
+            orientation == ui::Rotation::Rotation90 || orientation == ui::Rotation::Rotation270;
+    DisplayViewport v;
+    v.displayId = DISPLAY_ID;
+    v.orientation = orientation;
+    v.logicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.logicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.physicalRight = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.physicalBottom = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.deviceWidth = isRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
+    v.deviceHeight = isRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
+    v.isActive = true;
+    v.uniqueId = "local:1";
+    return v;
+}
+
+DisplayViewport createSecondaryViewport() {
+    DisplayViewport v;
+    v.displayId = SECONDARY_DISPLAY_ID;
+    v.orientation = ui::Rotation::Rotation0;
+    v.logicalRight = DISPLAY_HEIGHT;
+    v.logicalBottom = DISPLAY_WIDTH;
+    v.physicalRight = DISPLAY_HEIGHT;
+    v.physicalBottom = DISPLAY_WIDTH;
+    v.deviceWidth = DISPLAY_HEIGHT;
+    v.deviceHeight = DISPLAY_WIDTH;
+    v.isActive = true;
+    v.uniqueId = "local:2";
+    v.type = ViewportType::EXTERNAL;
+    return v;
+}
+
+/**
+ * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper.
+ *
+ * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates
+ * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input
+ * device doesn't set its associated viewport when it's configured.
+ *
+ * TODO(b/319217713): work out a way to avoid this fake.
+ */
+class ViewportFakingInputDeviceContext : public InputDeviceContext {
+public:
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
+                                     DisplayViewport viewport)
+          : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {}
+
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
+                                     ui::Rotation orientation)
+          : ViewportFakingInputDeviceContext(device, eventHubId,
+                                             createPrimaryViewport(orientation)) {}
+
+    std::optional<DisplayViewport> getAssociatedViewport() const override {
+        return mAssociatedViewport;
+    }
+
+    void setViewport(const DisplayViewport& viewport) { mAssociatedViewport = viewport; }
+
+private:
+    DisplayViewport mAssociatedViewport;
+};
+
+} // namespace
 
 namespace input_flags = com::android::input::flags;
 
 /**
  * Unit tests for CursorInputMapper.
- * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing
- * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name
- * can be simplified to 'CursorInputMapperTest'.
- * TODO(b/283812079): move CursorInputMapper tests here.
+ * These classes are named 'CursorInputMapperUnitTest...' to avoid name collision with the existing
+ * 'CursorInputMapperTest...' classes. If all of the CursorInputMapper tests are migrated here, the
+ * name can be simplified to 'CursorInputMapperTest'.
+ *
+ * TODO(b/283812079): move the remaining CursorInputMapper tests here. The ones that are left all
+ *   depend on viewport association, for which we'll need to fake InputDeviceContext.
  */
-class CursorInputMapperUnitTest : public InputMapperUnitTest {
+class CursorInputMapperUnitTestBase : public InputMapperUnitTest {
 protected:
-    void SetUp() override {
-        InputMapperUnitTest::SetUp();
+    void SetUp() override { SetUpWithBus(BUS_USB); }
+    void SetUpWithBus(int bus) override {
+        InputMapperUnitTest::SetUpWithBus(bus);
 
         // Current scan code state - all keys are UP by default
         setScanCodeState(KeyState::UP,
@@ -68,10 +152,11 @@
                 .WillRepeatedly(Return(false));
 
         mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-        mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                        /*isActive=*/true, "local:0", NO_PORT,
-                                        ViewportType::INTERNAL);
+        mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
+    }
 
+    void createMapper() {
+        createDevice();
         mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 
@@ -83,19 +168,55 @@
                 mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                      InputReaderConfiguration::Change::POINTER_CAPTURE);
         ASSERT_THAT(args,
-                    ElementsAre(
-                            VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID)))));
+                    ElementsAre(VariantWith<NotifyDeviceResetArgs>(
+                            AllOf(WithDeviceId(DEVICE_ID), WithEventTime(ARBITRARY_TIME)))));
 
         // Check that generation also got bumped
         ASSERT_GT(mDevice->getGeneration(), generation);
     }
+
+    void testMotionRotation(int32_t originalX, int32_t originalY, int32_t rotatedX,
+                            int32_t rotatedY) {
+        std::list<NotifyArgs> args;
+        args += process(ARBITRARY_TIME, EV_REL, REL_X, originalX);
+        args += process(ARBITRARY_TIME, EV_REL, REL_Y, originalY);
+        args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        ASSERT_THAT(args,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(ACTION_MOVE),
+                                  WithCoords(float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
+                                             float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD)))));
+    }
 };
 
+class CursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        CursorInputMapperUnitTestBase::SetUp();
+    }
+};
+
+TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsMouseInPointerMode) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mMapper->getSources());
+}
+
+TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsTrackballInNavigationMode) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mMapper->getSources());
+}
+
 /**
  * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering
  * ends. Currently, it is not.
  */
 TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) {
+    createMapper();
     std::list<NotifyArgs> args;
 
     // Move the cursor a little
@@ -139,6 +260,7 @@
  * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values.
  */
 TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) {
+    createMapper();
     setPointerCapture(true);
     std::list<NotifyArgs> args;
 
@@ -213,4 +335,1192 @@
                               WithRelativeMotion(10.0f, 20.0f)))));
 }
 
+TEST_F(CursorInputMapperUnitTest,
+       PopulateDeviceInfoReturnsRangeFromPointerControllerInPointerMode) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    mFakePolicy->clearViewports();
+    mFakePointerController->clearBounds();
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    // Initially there should not be a valid motion range because there's no viewport or pointer
+    // bounds.
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+
+    // When the bounds are set, then there should be a valid motion range.
+    mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
+    mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(systemTime(), mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    InputDeviceInfo info2;
+    mMapper->populateDeviceInfo(info2);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 1,
+                                              800 - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 2,
+                                              480 - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsScaledRangeInNavigationMode) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
+                                              -1.0f, 1.0f, 0.0f,
+                                              1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL,
+                                              -1.0f, 1.0f, 0.0f,
+                                              1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_TRACKBALL, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldSetAllFieldsAndIncludeGlobalMetaState) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    EXPECT_CALL(mMockInputReaderContext, getGlobalMetaState())
+            .WillRepeatedly(Return(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON));
+
+    std::list<NotifyArgs> args;
+
+    // Button press.
+    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPointerCount(1), WithPointerId(0, 0),
+                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
+                                          WithPressure(1.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPointerCount(1), WithPointerId(0, 0),
+                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
+                                          WithPressure(1.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME)))));
+    args.clear();
+
+    // Button release.  Should have same down time.
+    args += process(ARBITRARY_TIME + 1, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME + 1, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
+                                          WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(0), WithPointerCount(1),
+                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
+                                          WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(0), WithPointerCount(1),
+                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentXYUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Motion in X but not Y.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f),
+                              WithPressure(0.0f)))));
+    args.clear();
+
+    // Motion in Y but not X.
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithPressure(0.0f)))));
+    args.clear();
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentButtonUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Button press.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    // Button release.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleCombinedXYAndButtonUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Combined X, Y and Button.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithPressure(1.0f)))));
+    args.clear();
+
+    // Move X, Y a bit while pressed.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 2);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                         1.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithPressure(1.0f)))));
+    args.clear();
+
+    // Release Button.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+    args.clear();
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldNotRotateMotionsWhenOrientationAware) {
+    // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
+    // need to be rotated.
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    mPropertyMap.addProperty("cursor.orientationAware", "1");
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation90);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldRotateMotionsWhenNotOrientationAware) {
+    // Since InputReader works in the un-rotated coordinate space, only devices that are not
+    // orientation-aware are affected by display rotation.
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation0);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
+
+    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation90));
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1, -1));
+
+    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation180));
+    args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1, -1));
+
+    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation270));
+    args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1,  1));
+}
+
+TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesOrientationChanges) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    DisplayViewport viewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    mFakePointerController->setDisplayViewport(viewport);
+    mReaderConfiguration.setDisplayViewports({viewport});
+    createMapper();
+
+    // Verify that the coordinates are rotated.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithRelativeMotion(-20.0f, 10.0f)))));
+
+    // Enable Pointer Capture.
+    setPointerCapture(true);
+
+    // Move and verify rotation is not applied.
+    args = process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(ACTION_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithCoords(10.0f, 20.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) {
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+    // Set up the secondary display as the display on which the pointer should be shown. The
+    // InputDevice is not associated with any display.
+    mFakePointerController->setDisplayViewport(secondaryViewport);
+    mFakePointerController->setPosition(100, 200);
+    createMapper();
+
+    // Ensure input events are generated for the secondary display.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdWithAssociatedViewport) {
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+    // Set up the secondary display as the display on which the pointer should be shown.
+    mFakePointerController->setDisplayViewport(secondaryViewport);
+    mFakePointerController->setPosition(100, 200);
+    createDevice();
+    // Associate the InputDevice with the secondary display.
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    // Ensure input events are generated for the secondary display.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdIgnoresEventsForMismatchedPointerDisplay) {
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+    // Set up the primary display as the display on which the pointer should be shown.
+    mFakePointerController->setDisplayViewport(primaryViewport);
+    createDevice();
+    // Associate the InputDevice with the secondary display.
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    // The mapper should not generate any events because it is associated with a display that is
+    // different from the pointer display.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args, testing::IsEmpty());
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleAllButtons) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    // press BTN_LEFT, release BTN_LEFT
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f)))));
+    args.clear();
+
+    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f)))));
+}
+
+class CursorInputMapperButtonKeyTest
+      : public CursorInputMapperUnitTest,
+        public testing::WithParamInterface<
+                std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/,
+                           int32_t /*expectedKeyCode*/>> {};
+
+TEST_P(CursorInputMapperButtonKeyTest, ProcessShouldHandleButtonKey) {
+    auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam();
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                                             WithKeyCode(expectedKeyCode))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                                             WithKeyCode(expectedKeyCode)))));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        SideExtraBackAndForward, CursorInputMapperButtonKeyTest,
+        testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD),
+                        std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD,
+                                        AKEYCODE_FORWARD)));
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldMoveThePointerAroundInPointerMode) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(110.0f, 220.0f), WithPressure(0.0f), WithSize(0.0f),
+                              WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f),
+                              WithOrientation(0.0f), WithDistance(0.0f)))));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
+}
+
+/**
+ * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
+ * pointer acceleration or speed processing should not be applied.
+ */
+TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesVelocityProcessing) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
+                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
+    mReaderConfiguration.pointerVelocityControlParameters = testParams;
+    mFakePolicy->setVelocityControlParams(testParams);
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Move and verify scale is applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))));
+    NotifyMotionArgs motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+    args.clear();
+
+    // Enable Pointer Capture
+    setPointerCapture(true);
+
+    // Move and verify scale is not applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(10, 20)))));
+}
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
+class CursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        CursorInputMapperUnitTestBase::SetUp();
+    }
+};
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    mFakePolicy->clearViewports();
+    mFakePointerController->clearBounds();
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    // Initially there should not be a valid motion range because there's no viewport or pointer
+    // bounds.
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+
+    // When the viewport and the default pointer display ID is set, then there should be a valid
+    // motion range.
+    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+    mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(systemTime(), mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    InputDeviceInfo info2;
+    mMapper->populateDeviceInfo(info2);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0,
+                                              DISPLAY_WIDTH - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0,
+                                              DISPLAY_HEIGHT - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) {
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+    // Set up the secondary display as the display on which the pointer should be shown.
+    // The InputDevice is not associated with any display.
+    mFakePointerController->setDisplayViewport(secondaryViewport);
+    mFakePointerController->setPosition(100, 200);
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    // Ensure input events are generated for the secondary display.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer,
+       ConfigureDisplayIdShouldGenerateEventForMismatchedPointerDisplay) {
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+    // Set up the primary display as the display on which the pointer should be shown.
+    mFakePointerController->setDisplayViewport(primaryViewport);
+    createDevice();
+    // Associate the InputDevice with the secondary display.
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    // With PointerChoreographer enabled, there could be a PointerController for the associated
+    // display even if it is different from the pointer display. So the mapper should generate an
+    // event.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    // press BTN_LEFT, release BTN_LEFT
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f)))));
+    args.clear();
+
+    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+}
+
+class CursorInputMapperButtonKeyTestWithChoreographer
+      : public CursorInputMapperUnitTestWithChoreographer,
+        public testing::WithParamInterface<
+                std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/,
+                           int32_t /*expectedKeyCode*/>> {};
+
+TEST_P(CursorInputMapperButtonKeyTestWithChoreographer,
+       ProcessShouldHandleButtonKeyWithZeroCoords) {
+    auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam();
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                                             WithKeyCode(expectedKeyCode))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                                             WithKeyCode(expectedKeyCode)))));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        SideExtraBackAndForward, CursorInputMapperButtonKeyTestWithChoreographer,
+        testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD),
+                        std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD,
+                                        AKEYCODE_FORWARD)));
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(0.0f, 0.0f), WithPressure(0.0f), WithSize(0.0f),
+                              WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f),
+                              WithOrientation(0.0f), WithDistance(0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
+                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
+    mReaderConfiguration.pointerVelocityControlParameters = testParams;
+    mFakePolicy->setVelocityControlParams(testParams);
+    createMapper();
+
+    NotifyMotionArgs motionArgs;
+    std::list<NotifyArgs> args;
+
+    // Move and verify scale is applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))));
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+    args.clear();
+
+    // Enable Pointer Capture
+    setPointerCapture(true);
+
+    // Move and verify scale is not applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))));
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_EQ(10, relX2);
+    ASSERT_EQ(20, relY2);
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) {
+    // Set up the default display.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
+
+    // Set up the secondary display as the display on which the pointer should be shown.
+    // The InputDevice is not associated with any display.
+    mFakePolicy->addDisplayViewport(createSecondaryViewport());
+    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    // Ensure input events are generated without display ID or coords, because they will be decided
+    // later by PointerChoreographer.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE),
+                              WithCoords(0.0f, 0.0f)))));
+}
+
+namespace {
+
+// Minimum timestamp separation between subsequent input events from a Bluetooth device.
+constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
+// Maximum smoothing time delta so that we don't generate events too far into the future.
+constexpr nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
+
+} // namespace
+
+class BluetoothCursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        SetUpWithBus(BUS_BLUETOOTH);
+
+        mFakePointerController = std::make_shared<FakePointerController>();
+        mFakePolicy->setPointerController(mFakePointerController);
+    }
+};
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmoothening) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events that come in quick succession, according to their timestamps.
+    for (int i = 0; i < 3; i++) {
+        constexpr static nsecs_t delta = ms2ns(1);
+        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
+        kernelEventTime += delta;
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+        argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningIsCapped) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events with the same timestamp from the kernel.
+    // Ensure that we do not generate events too far into the future.
+    constexpr static int32_t numEvents =
+            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
+    for (int i = 0; i < numEvents; i++) {
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+
+    // By processing more events with the same timestamp, we should not generate events with a
+    // timestamp that is more than the specified max time delta from the timestamp at its injection.
+    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
+    for (int i = 0; i < 3; i++) {
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(cappedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningNotUsed) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
+    // smoothening is not needed, its timestamp is not affected.
+    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
+    expectedEventTime = kernelEventTime;
+
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+}
+
+// --- BluetoothCursorInputMapperUnitTestWithChoreographer ---
+
+class BluetoothCursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        SetUpWithBus(BUS_BLUETOOTH);
+
+        mFakePointerController = std::make_shared<FakePointerController>();
+        mFakePolicy->setPointerController(mFakePointerController);
+    }
+};
+
+TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmoothening) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events that come in quick succession, according to their timestamps.
+    for (int i = 0; i < 3; i++) {
+        constexpr static nsecs_t delta = ms2ns(1);
+        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
+        kernelEventTime += delta;
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+        argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningIsCapped) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events with the same timestamp from the kernel.
+    // Ensure that we do not generate events too far into the future.
+    constexpr static int32_t numEvents =
+            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
+    for (int i = 0; i < numEvents; i++) {
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+
+    // By processing more events with the same timestamp, we should not generate events with a
+    // timestamp that is more than the specified max time delta from the timestamp at its injection.
+    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
+    for (int i = 0; i < 3; i++) {
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(cappedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningNotUsed) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
+    // smoothening is not needed, its timestamp is not affected.
+    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
+    expectedEventTime = kernelEventTime;
+
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 212fceb..daa000f 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -431,6 +431,38 @@
     return -1;
 }
 
+void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis,
+                                   const std::vector<int32_t>& values) {
+    Device* device = getDevice(deviceId);
+    if (!device) {
+        FAIL() << "Missing device";
+    }
+    device->mtSlotValues[axis] = values;
+}
+
+base::Result<std::vector<int32_t>> FakeEventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                                 size_t slotCount) const {
+    Device* device = getDevice(deviceId);
+    if (!device) {
+        ADD_FAILURE() << "Missing device";
+        return base::ResultError("Missing device", UNKNOWN_ERROR);
+    }
+    const auto& mtSlotValuesIterator = device->mtSlotValues.find(axis);
+    if (mtSlotValuesIterator == device->mtSlotValues.end()) {
+        return base::ResultError("axis not supported", NAME_NOT_FOUND);
+    }
+    const auto& mtSlotValues = mtSlotValuesIterator->second;
+    if (mtSlotValues.size() != slotCount) {
+        ADD_FAILURE() << "MtSlot values specified for " << mtSlotValues.size()
+                      << " slots but expected for " << slotCount << " Slots";
+        return base::ResultError("Slot count mismatch", NAME_NOT_FOUND);
+    }
+    std::vector<int32_t> outValues(slotCount + 1);
+    outValues[0] = axis;
+    std::copy(mtSlotValues.begin(), mtSlotValues.end(), outValues.begin() + 1);
+    return std::move(outValues);
+}
+
 int32_t FakeEventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
     Device* device = getDevice(deviceId);
     if (!device) {
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index 8e06940..f07b344 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -65,6 +65,7 @@
         bool enabled;
         std::optional<RawLayoutInfo> layoutInfo;
         std::string sysfsRootPath;
+        std::unordered_map<int32_t, std::vector<int32_t>> mtSlotValues;
 
         status_t enable() {
             enabled = true;
@@ -154,6 +155,11 @@
                       int32_t value);
     void assertQueueIsEmpty();
     void setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const;
+    // Populate fake slot values to be returned by the getter, size of the values should be equal to
+    // the slot count
+    void setMtSlotValues(int32_t deviceId, int32_t axis, const std::vector<int32_t>& values);
+    base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                       size_t slotCount) const override;
 
 private:
     Device* getDevice(int32_t deviceId) const;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 80319f2..31e1173 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -28,6 +28,10 @@
     mMaxY = maxY;
 }
 
+void FakePointerController::clearBounds() {
+    mHaveBounds = false;
+}
+
 const std::map<int32_t, std::vector<int32_t>>& FakePointerController::getSpots() {
     return mSpotsByDisplay;
 }
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 800f864..061ae62 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -33,6 +33,7 @@
     virtual ~FakePointerController() {}
 
     void setBounds(float minX, float minY, float maxX, float maxY);
+    void clearBounds();
     const std::map<int32_t, std::vector<int32_t>>& getSpots();
 
     void setPosition(float x, float y) override;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 69772af..1630769 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -115,12 +115,32 @@
             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)))));
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            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));
+
+    // The same gesture again should only repeat the HOVER_MOVE and cursor position change, not the
+    // HOVER_ENTER.
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X - 10, POINTER_Y + 20),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 10, POINTER_Y + 20));
 }
 
 TEST_F(GestureConverterTest, Move_Rotated) {
@@ -134,10 +154,16 @@
             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)))));
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            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));
 }
@@ -153,8 +179,6 @@
                         /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
     std::list<NotifyArgs> args =
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
-    ASSERT_EQ(3u, args.size());
-
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
@@ -210,12 +234,32 @@
                                           WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
                                           WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
+TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE,
+                        /*is_tap=*/false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args.front(),
+                VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0),
+                              WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                              WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))));
+}
+
 TEST_F(GestureConverterTest, DragWithButton) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
@@ -275,7 +319,7 @@
                                           WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithButtonState(0),
                                           WithCoords(POINTER_X - 5, POINTER_Y + 10),
                                           WithToolType(ToolType::FINGER),
@@ -328,13 +372,20 @@
     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)))));
+                                    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))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
@@ -380,12 +431,19 @@
     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)))));
+                                    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))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) {
@@ -591,6 +649,12 @@
                                           WithMotionClassification(
                                                   MotionClassification::MULTI_FINGER_SWIPE),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -677,6 +741,9 @@
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                           WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -809,6 +876,12 @@
                                           WithMotionClassification(
                                                   MotionClassification::MULTI_FINGER_SWIPE),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -869,6 +942,12 @@
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -929,6 +1008,12 @@
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -1013,6 +1098,11 @@
                                     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_ENTER),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -1027,13 +1117,20 @@
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
     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)))));
+                                    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))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
@@ -1071,6 +1168,11 @@
                                           WithMotionClassification(
                                                   MotionClassification::MULTI_FINGER_SWIPE),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -1098,6 +1200,12 @@
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -1112,7 +1220,7 @@
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
 
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
                       WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
                       WithDisplayId(ADISPLAY_ID_DEFAULT)));
@@ -1131,13 +1239,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     std::list<NotifyArgs> args =
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, 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)))));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                        /* down= */ GESTURES_BUTTON_LEFT,
@@ -1146,6 +1248,12 @@
 
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          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_DOWN),
                                           WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0.f, 0.f),
@@ -1175,7 +1283,7 @@
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
                                           WithButtonState(0), WithPressure(0.0f),
@@ -1192,13 +1300,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     std::list<NotifyArgs> args =
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, 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)))));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
@@ -1207,6 +1309,12 @@
 
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          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_DOWN),
                                           WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0.f, 0.f),
@@ -1243,7 +1351,7 @@
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
                                           WithButtonState(0), WithPressure(0.0f),
@@ -1266,13 +1374,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     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)))));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
@@ -1301,13 +1403,7 @@
                          /* 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(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
@@ -1353,7 +1449,11 @@
                          /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
     args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
 
-    ASSERT_EQ(5u, args.size());
+    ASSERT_EQ(6u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                      WithRelativeMotion(0.f, 0.f), WithButtonState(0)));
+    args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithRelativeMotion(0.f, 0.f),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
@@ -1373,7 +1473,7 @@
                       WithButtonState(0)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithRelativeMotion(0, 0),
                       WithButtonState(0)));
 }
 
@@ -1390,13 +1490,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     std::list<NotifyArgs> args =
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, 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)))));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
@@ -1404,6 +1498,12 @@
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          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_DOWN),
                                           WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0.f, 0.f),
@@ -1441,7 +1541,7 @@
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
                                           WithButtonState(0), WithPressure(0.0f),
@@ -1463,14 +1563,7 @@
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     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));
+    // We don't need to check args here, since it's covered by the Move test.
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
@@ -1489,7 +1582,9 @@
             converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
 
     // Key presses with IME connection should cancel ongoing move gesture
     nsecs_t currentTime = gestureStartTime + 100;
@@ -1512,7 +1607,9 @@
     args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
 }
 
 // TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
@@ -1535,6 +1632,21 @@
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            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)))));
+
+    // The same gesture again should only repeat the HOVER_MOVE, not the HOVER_ENTER.
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
                               WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
                               WithButtonState(0), WithPressure(0.0f),
@@ -1552,10 +1664,16 @@
             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)))));
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            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) {
@@ -1621,12 +1739,32 @@
                                           WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithButtonState(0), WithCoords(0, 0),
                                           WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
+TEST_F(GestureConverterTestWithChoreographer, ButtonDownAfterMoveExitsHover) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE,
+                        /*is_tap=*/false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args.front(),
+                VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0),
+                              WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT))));
+}
+
 TEST_F(GestureConverterTestWithChoreographer, DragWithButton) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
@@ -1679,7 +1817,7 @@
                                           WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithButtonState(0), WithCoords(0, 0),
                                           WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
@@ -1730,12 +1868,20 @@
     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)))));
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(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))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) {
@@ -1781,11 +1927,19 @@
     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)))));
+                                    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))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) {
@@ -1990,6 +2144,12 @@
                                           WithMotionClassification(
                                                   MotionClassification::MULTI_FINGER_SWIPE),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2076,6 +2236,9 @@
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                           WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2208,6 +2371,12 @@
                                           WithMotionClassification(
                                                   MotionClassification::MULTI_FINGER_SWIPE),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2267,6 +2436,12 @@
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2326,6 +2501,12 @@
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2409,6 +2590,11 @@
                                     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_ENTER),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2423,12 +2609,20 @@
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
     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)))));
+                                    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))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) {
@@ -2466,6 +2660,11 @@
                                           WithMotionClassification(
                                                   MotionClassification::MULTI_FINGER_SWIPE),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2493,6 +2692,12 @@
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
                                           WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
                                           WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
@@ -2507,7 +2712,7 @@
             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),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0),
                       WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
                       WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
 }
@@ -2522,13 +2727,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     std::list<NotifyArgs> args =
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-
-    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)))));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                        /* down= */ GESTURES_BUTTON_LEFT,
@@ -2537,6 +2736,11 @@
 
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          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_DOWN),
                                           WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
                                           WithToolType(ToolType::FINGER),
@@ -2563,7 +2767,7 @@
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(0, 0), WithRelativeMotion(0, 0),
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f),
@@ -2580,12 +2784,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     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)));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
@@ -2594,6 +2793,11 @@
 
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          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_DOWN),
                                           WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
                                           WithToolType(ToolType::FINGER),
@@ -2628,7 +2832,7 @@
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(0, 0), WithRelativeMotion(0, 0),
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f),
@@ -2651,13 +2855,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     std::list<NotifyArgs> args =
             converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
-
-    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)))));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
@@ -2686,12 +2884,7 @@
                          /* 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)));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
@@ -2737,7 +2930,11 @@
                          /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
     args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
 
-    ASSERT_EQ(5u, args.size());
+    ASSERT_EQ(6u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                      WithRelativeMotion(0.f, 0.f), WithButtonState(0)));
+    args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithRelativeMotion(0.f, 0.f),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
@@ -2757,7 +2954,7 @@
                       WithButtonState(0)));
     args.pop_front();
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithRelativeMotion(0, 0),
                       WithButtonState(0)));
 }
 
@@ -2774,13 +2971,7 @@
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
     std::list<NotifyArgs> args =
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-
-    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)))));
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
@@ -2789,6 +2980,11 @@
 
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          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_DOWN),
                                           WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
                                           WithToolType(ToolType::FINGER),
@@ -2823,7 +3019,7 @@
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(0, 0), WithRelativeMotion(0, 0),
                                           WithToolType(ToolType::FINGER), WithButtonState(0),
                                           WithPressure(0.0f),
@@ -2845,13 +3041,7 @@
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     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)))));
+    // We don't need to check args here, since it's covered by the Move test.
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
@@ -2870,7 +3060,9 @@
             converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
 
     // Key presses with IME connection should cancel ongoing move gesture
     nsecs_t currentTime = gestureStartTime + 100;
@@ -2893,7 +3085,9 @@
     args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            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 c92736e..48d7e55 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1093,9 +1093,10 @@
 
     FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
                      const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
-                     int32_t displayId, std::optional<sp<IBinder>> token = std::nullopt)
+                     int32_t displayId, bool createInputChannel = true)
           : mName(name) {
-        if (token == std::nullopt) {
+        sp<IBinder> token;
+        if (createInputChannel) {
             base::Result<std::unique_ptr<InputChannel>> channel =
                     dispatcher->createInputChannel(name);
             token = (*channel)->getConnectionToken();
@@ -1105,7 +1106,7 @@
         inputApplicationHandle->updateInfo();
         mInfo.applicationInfo = *inputApplicationHandle->getInfo();
 
-        mInfo.token = *token;
+        mInfo.token = token;
         mInfo.id = sId++;
         mInfo.name = name;
         mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
@@ -1679,6 +1680,24 @@
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 }
 
+using InputDispatcherDeathTest = InputDispatcherTest;
+
+/**
+ * When 'onWindowInfosChanged' arguments contain a duplicate entry for the same window, dispatcher
+ * should crash.
+ */
+TEST_F(InputDispatcherDeathTest, DuplicateWindowInfosAbortDispatcher) {
+    testing::GTEST_FLAG(death_test_style) = "threadsafe";
+    ScopedSilentDeath _silentDeath;
+
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    ASSERT_DEATH(mDispatcher->onWindowInfosChanged(
+                         {{*window->getInfo(), *window->getInfo()}, {}, 0, 0}),
+                 "Incorrect WindowInfosUpdate provided");
+}
+
 TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
@@ -2370,7 +2389,7 @@
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
                                        ADISPLAY_ID_DEFAULT,
-                                       /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+                                       /*createInputChannel=*/false);
     obscuringWindow->setFrame(Rect(0, 0, 200, 200));
     obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
     obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
@@ -2419,7 +2438,7 @@
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
                                        ADISPLAY_ID_DEFAULT,
-                                       /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+                                       /*createInputChannel=*/false);
     obscuringWindow->setFrame(Rect(0, 0, 200, 200));
     obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
     obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID);
@@ -6973,6 +6992,39 @@
     mWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherFallbackKeyTest, WindowRemovedDuringPolicyCall) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    mFakePolicy->setUnhandledKeyHandler([&](const KeyEvent& event) {
+        // When the unhandled key is reported to the policy next, remove the input channel.
+        mDispatcher->removeInputChannel(mWindow->getToken());
+        return KeyEventBuilder(event).keyCode(AKEYCODE_B).build();
+    });
+    // Release the original key, and let the app now handle the previously unhandled key.
+    // This should result in the previously generated fallback key to be cancelled.
+    // Since the policy was notified of the unhandled DOWN event earlier, it will also be notified
+    // of the UP event for consistency. The Dispatcher calls into the policy from its own thread
+    // without holding the lock, because it need to synchronously fetch the fallback key. While in
+    // the policy call, we will now remove the input channel. Once the policy call returns, the
+    // Dispatcher will no longer have a channel to send cancellation events to. Ensure this does
+    // not cause any crashes.
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+}
+
 class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
 protected:
     static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms;
@@ -7332,6 +7384,94 @@
     monitorInSecondary.assertNoEvents();
 }
 
+/**
+ * Send a key to the primary display and to the secondary display.
+ * Then cause the key on the primary display to be canceled by sending in a stale key.
+ * Ensure that the key on the primary display is canceled, and that the key on the secondary display
+ * does not get canceled.
+ */
+TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropKeyEvent_OnlyCancelCorrespondingKeyGesture) {
+    // Send a key down on primary display
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build());
+    windowInPrimary->consumeKeyEvent(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    windowInSecondary->assertNoEvents();
+
+    // Send a key down on second display
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                    .displayId(SECOND_DISPLAY_ID)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build());
+    windowInSecondary->consumeKeyEvent(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(SECOND_DISPLAY_ID)));
+    windowInPrimary->assertNoEvents();
+
+    // Send a valid key up event on primary display that will be dropped because it is stale
+    NotifyKeyArgs staleKeyUp =
+            KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build();
+    static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms;
+    mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT);
+    std::this_thread::sleep_for(STALE_EVENT_TIMEOUT);
+    mDispatcher->notifyKey(staleKeyUp);
+
+    // Only the key gesture corresponding to the dropped event should receive the cancel event.
+    // Therefore, windowInPrimary should get the cancel event and windowInSecondary should not
+    // receive any events.
+    windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                           WithDisplayId(ADISPLAY_ID_DEFAULT),
+                                           WithFlags(AKEY_EVENT_FLAG_CANCELED)));
+    windowInSecondary->assertNoEvents();
+}
+
+/**
+ * Similar to 'WhenDropKeyEvent_OnlyCancelCorrespondingKeyGesture' but for motion events.
+ */
+TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropMotionEvent_OnlyCancelCorrespondingGesture) {
+    // Send touch down on primary display.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .build());
+    windowInPrimary->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    windowInSecondary->assertNoEvents();
+
+    // Send touch down on second display.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .displayId(SECOND_DISPLAY_ID)
+                    .build());
+    windowInPrimary->assertNoEvents();
+    windowInSecondary->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(SECOND_DISPLAY_ID)));
+
+    // inject a valid MotionEvent on primary display that will be stale when it arrives.
+    NotifyMotionArgs staleMotionUp =
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
+                    .build();
+    static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms;
+    mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT);
+    std::this_thread::sleep_for(STALE_EVENT_TIMEOUT);
+    mDispatcher->notifyMotion(staleMotionUp);
+
+    // For stale motion events, we let the gesture to complete. This behaviour is different from key
+    // events, where we would cancel the current keys instead.
+    windowInPrimary->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    windowInSecondary->assertNoEvents();
+}
+
 class InputFilterTest : public InputDispatcherTest {
 protected:
     void testNotifyMotion(int32_t displayId, bool expectToBeFiltered,
@@ -7654,8 +7794,7 @@
                                               ADISPLAY_ID_DEFAULT);
         mWindow1->setFrame(Rect(0, 0, 100, 100));
 
-        mWindow2 = sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window 2",
-                                              ADISPLAY_ID_DEFAULT, mWindow1->getToken());
+        mWindow2 = mWindow1->clone(ADISPLAY_ID_DEFAULT);
         mWindow2->setFrame(Rect(100, 100, 200, 200));
 
         mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
@@ -8922,7 +9061,7 @@
         mNoInputWindow =
                 sp<FakeWindowHandle>::make(mApplication, mDispatcher,
                                            "Window without input channel", ADISPLAY_ID_DEFAULT,
-                                           /*token=*/std::make_optional<sp<IBinder>>(nullptr));
+                                           /*createInputChannel=*/false);
         mNoInputWindow->setNoInputChannel(true);
         mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
         // It's perfectly valid for this window to not have an associated input channel
@@ -8990,8 +9129,7 @@
         InputDispatcherTest::SetUp();
         mApp = std::make_shared<FakeApplicationHandle>();
         mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
-        mMirror = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindowMirror",
-                                             ADISPLAY_ID_DEFAULT, mWindow->getToken());
+        mMirror = mWindow->clone(ADISPLAY_ID_DEFAULT);
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
         mWindow->setFocusable(true);
         mMirror->setFocusable(true);
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 36be684..2aecab9 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -19,12 +19,16 @@
 #include <InputReaderBase.h>
 #include <gtest/gtest.h>
 #include <ui/Rotation.h>
+#include <utils/Timers.h>
+
+#include "NotifyArgs.h"
 
 namespace android {
 
+using testing::_;
 using testing::Return;
 
-void InputMapperUnitTest::SetUp() {
+void InputMapperUnitTest::SetUpWithBus(int bus) {
     mFakePointerController = std::make_shared<FakePointerController>();
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
@@ -36,20 +40,30 @@
     EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get()));
 
     EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
-    InputDeviceIdentifier identifier;
-    identifier.name = "device";
-    identifier.location = "USB1";
-    identifier.bus = 0;
 
-    EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier));
+    mIdentifier.name = "device";
+    mIdentifier.location = "USB1";
+    mIdentifier.bus = bus;
+    EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID))
+            .WillRepeatedly(Return(mIdentifier));
+    EXPECT_CALL(mMockEventHub, getConfiguration(EVENTHUB_ID)).WillRepeatedly([&](int32_t) {
+        return mPropertyMap;
+    });
+}
+
+void InputMapperUnitTest::createDevice() {
     mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
-                                            /*generation=*/2, identifier);
+                                            /*generation=*/2, mIdentifier);
+    mDevice->addEmptyEventHubDevice(EVENTHUB_ID);
     mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
+    std::list<NotifyArgs> args =
+            mDevice->configure(systemTime(), mReaderConfiguration, /*changes=*/{});
+    ASSERT_THAT(args, testing::ElementsAre(testing::VariantWith<NotifyDeviceResetArgs>(_)));
 }
 
 void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
                                     int32_t resolution) {
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, _))
             .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
                 outAxisInfo->valid = valid;
                 outAxisInfo->minValue = min;
@@ -215,8 +229,8 @@
     return generatedArgs;
 }
 
-void InputMapperTest::assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source,
-                                        float min, float max, float flat, float fuzz) {
+void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min,
+                       float max, float flat, float fuzz) {
     const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source);
     ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source;
     ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source;
@@ -227,11 +241,9 @@
     ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source;
 }
 
-void InputMapperTest::assertPointerCoords(const PointerCoords& coords, float x, float y,
-                                          float pressure, float size, float touchMajor,
-                                          float touchMinor, float toolMajor, float toolMinor,
-                                          float orientation, float distance,
-                                          float scaledAxisEpsilon) {
+void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size,
+                         float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+                         float orientation, float distance, float scaledAxisEpsilon) {
     ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon);
     ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon);
     ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON);
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 05b0e97..e176a65 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -32,6 +32,7 @@
 #include "InterfaceMocks.h"
 #include "TestConstants.h"
 #include "TestInputListener.h"
+#include "input/PropertyMap.h"
 
 namespace android {
 
@@ -41,7 +42,15 @@
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr float INITIAL_CURSOR_X = 400;
     static constexpr float INITIAL_CURSOR_Y = 240;
-    virtual void SetUp() override;
+    virtual void SetUp() override { SetUpWithBus(0); }
+    virtual void SetUpWithBus(int bus);
+
+    /**
+     * Initializes mDevice and mDeviceContext. When this happens, mDevice takes a copy of
+     * mPropertyMap, so tests that need to set configuration properties should do so before calling
+     * this. Others will most likely want to call it in their SetUp method.
+     */
+    void createDevice();
 
     void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
 
@@ -54,6 +63,7 @@
     std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);
     std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value);
 
+    InputDeviceIdentifier mIdentifier;
     MockEventHubInterface mMockEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
     std::shared_ptr<FakePointerController> mFakePointerController;
@@ -64,6 +74,7 @@
     InputReaderConfiguration mReaderConfiguration;
     // The mapper should be created by the subclasses.
     std::unique_ptr<InputMapper> mMapper;
+    PropertyMap mPropertyMap;
 };
 
 /**
@@ -130,13 +141,13 @@
     void resetMapper(InputMapper& mapper, nsecs_t when);
 
     std::list<NotifyArgs> handleTimeout(InputMapper& mapper, nsecs_t when);
-
-    static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source,
-                                  float min, float max, float flat, float fuzz);
-    static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure,
-                                    float size, float touchMajor, float touchMinor, float toolMajor,
-                                    float toolMinor, float orientation, float distance,
-                                    float scaledAxisEpsilon = 1.f);
 };
 
+void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min,
+                       float max, float flat, float fuzz);
+
+void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size,
+                         float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+                         float orientation, float distance, float scaledAxisEpsilon = 1.f);
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index c6536de..460a7b1 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -97,8 +97,6 @@
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
 static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
-// Maximum smoothing time delta so that we don't generate events too far into the future.
-constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
 
 namespace input_flags = com::android::input::flags;
 
@@ -1352,6 +1350,9 @@
 
     std::shared_ptr<FakePointerController> mFakePointerController;
 
+    constexpr static auto EVENT_HAPPENED_TIMEOUT = 2000ms;
+    constexpr static auto EVENT_DID_NOT_HAPPEN_TIMEOUT = 30ms;
+
     void SetUp() override {
 #if !defined(__ANDROID__)
         GTEST_SKIP();
@@ -1383,8 +1384,8 @@
     }
 
     void setupInputReader() {
-        mTestListener = std::make_unique<TestInputListener>(/*eventHappenedTimeout=*/2000ms,
-                                                            /*eventDidNotHappenTimeout=*/30ms);
+        mTestListener = std::make_unique<TestInputListener>(EVENT_HAPPENED_TIMEOUT,
+                                                            EVENT_DID_NOT_HAPPEN_TIMEOUT);
 
         mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy,
                                                 *mTestListener);
@@ -2415,17 +2416,29 @@
     mDevice->sendTrackingId(FIRST_TRACKING_ID);
     mDevice->sendToolType(MT_TOOL_FINGER);
     mDevice->sendDown(centerPoint);
-    auto waitUntil = std::chrono::system_clock::now() +
-            std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
+    const auto syncTime = std::chrono::system_clock::now();
+    // After 72 ms, the event *will* be generated. If we wait the full 72 ms to check that NO event
+    // is generated in that period, there will be a race condition between the event being generated
+    // and the test's wait timeout expiring. Thus, we wait for a shorter duration in the test, which
+    // will reduce the liklihood of the race condition occurring.
+    const auto waitUntilTimeForNoEvent =
+            syncTime + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT / 2));
     mDevice->sendSync();
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntil));
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntilTimeForNoEvent));
 
     // Since the external stylus did not report a pressure value within the timeout,
     // it shows up as a finger pointer.
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
-                  WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), WithPressure(1.f))));
+    const auto waitUntilTimeForEvent = syncTime +
+            std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)) + EVENT_HAPPENED_TIMEOUT;
+    ASSERT_NO_FATAL_FAILURE(
+            mTestListener->assertNotifyMotionWasCalled(AllOf(WithMotionAction(
+                                                                     AMOTION_EVENT_ACTION_DOWN),
+                                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN |
+                                                                        AINPUT_SOURCE_STYLUS),
+                                                             WithToolType(ToolType::FINGER),
+                                                             WithDeviceId(touchscreenId),
+                                                             WithPressure(1.f)),
+                                                       waitUntilTimeForEvent));
 
     // Change the pressure on the external stylus. Since the pressure was not present at the start
     // of the gesture, it is ignored for now.
@@ -2906,13 +2919,11 @@
     mapper.assertProcessWasCalled();
 
     // Simulate a kernel buffer overflow, which generates a SYN_DROPPED event.
-    // This should reset the mapper.
     event.type = EV_SYN;
     event.code = SYN_DROPPED;
     event.value = 0;
     unused = mDevice->process(&event, /*count=*/1);
     mapper.assertProcessWasNotCalled();
-    mapper.assertResetWasCalled();
 
     // All events until the next SYN_REPORT should be dropped.
     event.type = EV_KEY;
@@ -2922,11 +2933,13 @@
     mapper.assertProcessWasNotCalled();
 
     // We get the SYN_REPORT event now, which is not forwarded to mappers.
+    // This should reset the mapper.
     event.type = EV_SYN;
     event.code = SYN_REPORT;
     event.value = 0;
     unused = mDevice->process(&event, /*count=*/1);
     mapper.assertProcessWasNotCalled();
+    mapper.assertResetWasCalled();
 
     // The mapper receives events normally now.
     event.type = EV_KEY;
@@ -4087,7 +4100,7 @@
     void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); }
 };
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) {
+TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) {
     // For external devices, keys will trigger wake on key down. Media keys should also trigger
     // wake if triggered from external devices.
 
@@ -4126,6 +4139,36 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
+TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboard) {
+    // For external devices, keys will trigger wake on key down. Media keys should not trigger
+    // wake if triggered from external non-alphaebtic keyboard (e.g. headsets).
+
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE,
+                          POLICY_FLAG_WAKE);
+
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
+    NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+}
+
 TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) {
     // Tv Remote key's wake behavior is prescribed by the keylayout file.
 
@@ -4164,1492 +4207,6 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
-// --- CursorInputMapperTestBase ---
-
-class CursorInputMapperTestBase : public InputMapperTest {
-protected:
-    static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
-
-    std::shared_ptr<FakePointerController> mFakePointerController;
-
-    void SetUp() override {
-        InputMapperTest::SetUp();
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
-
-    void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY,
-                            int32_t rotatedX, int32_t rotatedY);
-
-    void prepareDisplay(ui::Rotation orientation) {
-        setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation,
-                                     DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    }
-
-    void prepareSecondaryDisplay() {
-        setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                     ui::ROTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT,
-                                     ViewportType::EXTERNAL);
-    }
-
-    static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y,
-                                          float pressure) {
-        ASSERT_NO_FATAL_FAILURE(assertPointerCoords(coords, x, y, pressure, 0.0f, 0.0f, 0.0f, 0.0f,
-                                                    0.0f, 0.0f, 0.0f, EPSILON));
-    }
-};
-
-const int32_t CursorInputMapperTestBase::TRACKBALL_MOVEMENT_THRESHOLD = 6;
-
-void CursorInputMapperTestBase::testMotionRotation(CursorInputMapper& mapper, int32_t originalX,
-                                                   int32_t originalY, int32_t rotatedX,
-                                                   int32_t rotatedY) {
-    NotifyMotionArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, originalY);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(args.pointerCoords[0],
-                                      float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
-                                      float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
-}
-
-// --- CursorInputMapperTest ---
-
-class CursorInputMapperTest : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        CursorInputMapperTestBase::SetUp();
-    }
-};
-
-TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources());
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    // Initially there may not be a valid motion range.
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-
-    // When the bounds are set, then there should be a valid motion range.
-    mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    InputDeviceInfo info2;
-    mapper.populateDeviceInfo(info2);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE,
-            1, 800 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE,
-            2, 480 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE,
-            0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
-            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL,
-            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL,
-            0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
-
-    NotifyMotionArgs args;
-
-    // Button press.
-    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Button release.  Should have same down time.
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(0, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(0, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Motion in X but not Y.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f,
-                                                      0.0f));
-
-    // Motion in Y but not X.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Button press.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    // Button release.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Combined X, Y and Button.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    // Move X, Y a bit while pressed.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    // Release Button.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) {
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
-    addConfigurationProperty("cursor.mode", "navigation");
-    // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
-    // need to be rotated.
-    addConfigurationProperty("cursor.orientationAware", "1");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    prepareDisplay(ui::ROTATION_90);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
-}
-
-TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotions) {
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
-    addConfigurationProperty("cursor.mode", "navigation");
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_0);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_90);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1, -1));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_180);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1, -1));
-
-    clearViewports();
-    prepareDisplay(ui::ROTATION_270);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1,  1));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs motionArgs;
-    NotifyKeyArgs keyArgs;
-
-    // press BTN_LEFT, release BTN_LEFT
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    // press BTN_BACK, release BTN_BACK
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_SIDE, release BTN_SIDE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_FORWARD, release BTN_FORWARD
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    // press BTN_EXTRA, release BTN_EXTRA
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-}
-
-TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-/**
- * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
- * pointer acceleration or speed processing should not be applied.
- */
-TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
-                                               /*high threshold=*/100.f, /*acceleration=*/10.f);
-    mFakePolicy->setVelocityControlParams(testParams);
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    NotifyMotionArgs args;
-
-    // Move and verify scale is applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_GT(relX, 10);
-    ASSERT_GT(relY, 20);
-
-    // Enable Pointer Capture
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    NotifyPointerCaptureChangedArgs captureArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
-    ASSERT_TRUE(captureArgs.request.enable);
-
-    // Move and verify scale is not applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(10, args.pointerCoords[0].getX());
-    ASSERT_EQ(20, args.pointerCoords[0].getY());
-}
-
-TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    // Ensure the display is rotated.
-    prepareDisplay(ui::ROTATION_90);
-
-    NotifyMotionArgs args;
-
-    // Verify that the coordinates are rotated.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_EQ(-20, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X));
-    ASSERT_EQ(10, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
-
-    // Enable Pointer Capture.
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    NotifyPointerCaptureChangedArgs captureArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
-    ASSERT_TRUE(captureArgs.request.enable);
-
-    // Move and verify rotation is not applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(10, args.pointerCoords[0].getX());
-    ASSERT_EQ(20, args.pointerCoords[0].getY());
-}
-
-TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown.
-    // The InputDevice is not associated with any display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    // Ensure input events are generated for the secondary display.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(110.0f, 220.0f))));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown,
-    // and associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(110.0f, 220.0f))));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display as the display on which the pointer should be shown.
-    prepareDisplay(ui::ROTATION_90);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-
-    // Associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    // The mapper should not generate any events because it is associated with a display that is
-    // different from the pointer display.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-}
-
-// --- CursorInputMapperTestWithChoreographer ---
-
-// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
-//   logic can be removed.
-class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(true);
-        CursorInputMapperTestBase::SetUp();
-    }
-};
-
-TEST_F(CursorInputMapperTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    // Initially there may not be a valid motion range.
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
-                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-
-    // When the viewport and the default pointer display ID is set, then there should be a valid
-    // motion range.
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    InputDeviceInfo info2;
-    mapper.populateDeviceInfo(info2);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0,
-                                              DISPLAY_WIDTH - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0,
-                                              DISPLAY_HEIGHT - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
-                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    prepareDisplay(ui::ROTATION_0);
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs motionArgs;
-    NotifyKeyArgs keyArgs;
-
-    // press BTN_LEFT, release BTN_LEFT
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    // press BTN_BACK, release BTN_BACK
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_SIDE, release BTN_SIDE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_FORWARD, release BTN_FORWARD
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    // press BTN_EXTRA, release BTN_EXTRA
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    prepareDisplay(ui::ROTATION_0);
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                                                0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
-                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
-    mFakePolicy->setVelocityControlParams(testParams);
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    prepareDisplay(ui::ROTATION_0);
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    NotifyMotionArgs args;
-
-    // Move and verify scale is applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_GT(relX, 10);
-    ASSERT_GT(relY, 20);
-
-    // Enable Pointer Capture
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    NotifyPointerCaptureChangedArgs captureArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
-    ASSERT_TRUE(captureArgs.request.enable);
-
-    // Move and verify scale is not applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    const float relX2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_EQ(10, relX2);
-    ASSERT_EQ(20, relY2);
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown.
-    // The InputDevice is not associated with any display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    // Ensure input events are generated without display ID and coords,
-    // because they will be decided later by PointerChoreographer.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE),
-                  WithCoords(0.0f, 0.0f))));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_90);
-
-    // Set up the secondary display as the display on which the pointer should be shown,
-    // and associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    // Ensure input events are generated with associated display ID but not with coords,
-    // because the coords will be decided later by PointerChoreographer.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(0.0f, 0.0f))));
-}
-
-TEST_F(CursorInputMapperTestWithChoreographer,
-       ConfigureDisplayIdShouldGenerateEventWithMismatchedPointerDisplay) {
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display as the display on which the pointer should be shown.
-    prepareDisplay(ui::ROTATION_90);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-
-    // Associate the InputDevice with the secondary display.
-    prepareSecondaryDisplay();
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    // With PointerChoreographer enabled, there could be a PointerController for the associated
-    // display even if it is different from the pointer display. So the mapper should generate an
-    // event.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
-                  WithCoords(0.0f, 0.0f))));
-}
-
-// --- BluetoothCursorInputMapperTest ---
-
-class BluetoothCursorInputMapperTest : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
-};
-
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events that come in quick succession, according to their timestamps.
-    for (int i = 0; i < 3; i++) {
-        constexpr static nsecs_t delta = ms2ns(1);
-        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
-        kernelEventTime += delta;
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events with the same timestamp from the kernel.
-    // Ensure that we do not generate events too far into the future.
-    constexpr static int32_t numEvents =
-            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
-    for (int i = 0; i < numEvents; i++) {
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-
-    // By processing more events with the same timestamp, we should not generate events with a
-    // timestamp that is more than the specified max time delta from the timestamp at its injection.
-    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
-    for (int i = 0; i < 3; i++) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(cappedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
-    // smoothening is not needed, its timestamp is not affected.
-    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
-    expectedEventTime = kernelEventTime;
-
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-}
-
-// --- BluetoothCursorInputMapperTestWithChoreographer ---
-
-class BluetoothCursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(true);
-        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
-};
-
-TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmoothening) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_0);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events that come in quick succession, according to their timestamps.
-    for (int i = 0; i < 3; i++) {
-        constexpr static nsecs_t delta = ms2ns(1);
-        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
-        kernelEventTime += delta;
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningIsCapped) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_0);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events with the same timestamp from the kernel.
-    // Ensure that we do not generate events too far into the future.
-    constexpr static int32_t numEvents =
-            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
-    for (int i = 0; i < numEvents; i++) {
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-
-    // By processing more events with the same timestamp, we should not generate events with a
-    // timestamp that is more than the specified max time delta from the timestamp at its injection.
-    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
-    for (int i = 0; i < 3; i++) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(cappedEventTime))));
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningNotUsed) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    // Set up the default display.
-    prepareDisplay(ui::ROTATION_0);
-    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
-    // smoothening is not needed, its timestamp is not affected.
-    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
-    expectedEventTime = kernelEventTime;
-
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-}
-
 // --- TouchInputMapperTest ---
 
 class TouchInputMapperTest : public InputMapperTest {
@@ -10971,15 +9528,16 @@
     ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
 }
 
-TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
+TEST_F(MultiTouchInputMapperTest, Reset_RepopulatesMultiTouchState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // First finger down.
+    constexpr int32_t x1 = 100, y1 = 200, x2 = 300, y2 = 400;
     processId(mapper, FIRST_TRACKING_ID);
-    processPosition(mapper, 100, 200);
+    processPosition(mapper, x1, y1);
     processPressure(mapper, RAW_PRESSURE_MAX);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
@@ -10988,14 +9546,32 @@
     // Second finger down.
     processSlot(mapper, SECOND_SLOT);
     processId(mapper, SECOND_TRACKING_ID);
-    processPosition(mapper, 300, 400);
+    processPosition(mapper, x2, y2);
     processPressure(mapper, RAW_PRESSURE_MAX);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(
             mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN)));
 
+    // Set MT Slot state to be repopulated for the required slots
+    std::vector<int32_t> mtSlotValues(RAW_SLOT_MAX + 1, -1);
+    mtSlotValues[0] = FIRST_TRACKING_ID;
+    mtSlotValues[1] = SECOND_TRACKING_ID;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_TRACKING_ID, mtSlotValues);
+
+    mtSlotValues[0] = x1;
+    mtSlotValues[1] = x2;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_X, mtSlotValues);
+
+    mtSlotValues[0] = y1;
+    mtSlotValues[1] = y2;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_Y, mtSlotValues);
+
+    mtSlotValues[0] = RAW_PRESSURE_MAX;
+    mtSlotValues[1] = RAW_PRESSURE_MAX;
+    mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_PRESSURE, mtSlotValues);
+
     // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
-    // preserved. Resetting should cancel the ongoing gesture.
+    // repopulated. Resetting should cancel the ongoing gesture.
     resetMapper(mapper, ARBITRARY_TIME);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 7394913..db89168 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -48,7 +48,7 @@
 class MockInputReaderContext : public InputReaderContext {
 public:
     MOCK_METHOD(void, updateGlobalMetaState, (), (override));
-    int32_t getGlobalMetaState() override { return 0; };
+    MOCK_METHOD(int32_t, getGlobalMetaState, (), (override));
 
     MOCK_METHOD(void, disableVirtualKeysUntil, (nsecs_t time), (override));
     MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode),
@@ -132,6 +132,9 @@
 
     MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue),
                 (const, override));
+    MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues,
+                (int32_t deviceId, int32_t axis, size_t slotCount), (const, override));
+
     MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t deviceId, int32_t locationKeyCode),
                 (const, override));
     MOCK_METHOD(bool, markSupportedKeyCodes,
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 2ef7999..b44529b 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -55,6 +55,7 @@
 
     void SetUp() override {
         InputMapperUnitTest::SetUp();
+        createDevice();
 
         // set key-codes expected in tests
         for (const auto& [scanCode, outKeycode] : mKeyCodeMap) {
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
new file mode 100644
index 0000000..d726385
--- /dev/null
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MultiTouchInputMapper.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <list>
+#include <optional>
+
+#include "InputMapperTest.h"
+#include "InterfaceMocks.h"
+#include "TestEventMatchers.h"
+
+#define TAG "MultiTouchpadInputMapperUnit_test"
+
+namespace android {
+
+using testing::_;
+using testing::IsEmpty;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::VariantWith;
+
+static constexpr int32_t DISPLAY_ID = 0;
+static constexpr int32_t DISPLAY_WIDTH = 480;
+static constexpr int32_t DISPLAY_HEIGHT = 800;
+static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+static constexpr int32_t SLOT_COUNT = 5;
+
+static constexpr int32_t ACTION_POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t ACTION_POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+/**
+ * Unit tests for MultiTouchInputMapper.
+ */
+class MultiTouchInputMapperUnitTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+
+        // Present scan codes
+        expectScanCodes(/*present=*/true,
+                        {BTN_TOUCH, BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP,
+                         BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
+
+        // Missing scan codes that the mapper checks for.
+        expectScanCodes(/*present=*/false,
+                        {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL,
+                         BTN_TOOL_AIRBRUSH});
+
+        // Current scan code state - all keys are UP by default
+        setScanCodeState(KeyState::UP, {BTN_LEFT,           BTN_RIGHT,        BTN_MIDDLE,
+                                        BTN_BACK,           BTN_SIDE,         BTN_FORWARD,
+                                        BTN_EXTRA,          BTN_TASK,         BTN_TOUCH,
+                                        BTN_STYLUS,         BTN_STYLUS2,      BTN_0,
+                                        BTN_TOOL_FINGER,    BTN_TOOL_PEN,     BTN_TOOL_RUBBER,
+                                        BTN_TOOL_BRUSH,     BTN_TOOL_PENCIL,  BTN_TOOL_AIRBRUSH,
+                                        BTN_TOOL_MOUSE,     BTN_TOOL_LENS,    BTN_TOOL_DOUBLETAP,
+                                        BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
+
+        setKeyCodeState(KeyState::UP,
+                        {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY});
+
+        // Input properties - only INPUT_PROP_DIRECT for touchscreen
+        EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, _)).WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT))
+                .WillRepeatedly(Return(true));
+
+        // Axes that the device has
+        setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/SLOT_COUNT - 1, /*resolution=*/0);
+        setupAxis(ABS_MT_TRACKING_ID, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0);
+        setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24);
+        setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24);
+
+        // Axes that the device does not have
+        setupAxis(ABS_MT_PRESSURE, /*valid=*/false, /*min*/ 0, /*max=*/255, /*resolution=*/0);
+        setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+
+        // reset current slot at the beginning
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
+                .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) {
+                    *outValue = 0;
+                    return OK;
+                });
+
+        // mark all slots not in use
+        mockSlotValues({});
+
+        mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+        mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                        /*isActive=*/true, "local:0", NO_PORT,
+                                        ViewportType::INTERNAL);
+        createDevice();
+        mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
+                                                           mFakePolicy->getReaderConfiguration());
+    }
+
+    // Mocks position and tracking Ids for the provided slots. Remaining slots will be marked
+    // unused.
+    void mockSlotValues(
+            const std::unordered_map<int32_t /*slotIndex*/,
+                                     std::pair<Point /*position*/, int32_t /*trackingId*/>>&
+                    slotValues) {
+        EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, _, SLOT_COUNT))
+                .WillRepeatedly([=](int32_t, int32_t axis,
+                                    size_t slotCount) -> base::Result<std::vector<int32_t>> {
+                    // tracking Id for the unused slots must set to be < 0
+                    std::vector<int32_t> outMtSlotValues(slotCount + 1, -1);
+                    outMtSlotValues[0] = axis;
+                    switch (axis) {
+                        case ABS_MT_POSITION_X:
+                            for (const auto& [slotIndex, valuePair] : slotValues) {
+                                outMtSlotValues[slotIndex] = valuePair.first.x;
+                            }
+                            return outMtSlotValues;
+                        case ABS_MT_POSITION_Y:
+                            for (const auto& [slotIndex, valuePair] : slotValues) {
+                                outMtSlotValues[slotIndex] = valuePair.first.y;
+                            }
+                            return outMtSlotValues;
+                        case ABS_MT_TRACKING_ID:
+                            for (const auto& [slotIndex, valuePair] : slotValues) {
+                                outMtSlotValues[slotIndex] = valuePair.second;
+                            }
+                            return outMtSlotValues;
+                        default:
+                            return base::ResultError("Axis not supported", NAME_NOT_FOUND);
+                    }
+                });
+    }
+
+    std::list<NotifyArgs> processPosition(int32_t x, int32_t y) {
+        std::list<NotifyArgs> args;
+        args += process(EV_ABS, ABS_MT_POSITION_X, x);
+        args += process(EV_ABS, ABS_MT_POSITION_Y, y);
+        return args;
+    }
+
+    std::list<NotifyArgs> processId(int32_t id) { return process(EV_ABS, ABS_MT_TRACKING_ID, id); }
+
+    std::list<NotifyArgs> processKey(int32_t code, int32_t value) {
+        return process(EV_KEY, code, value);
+    }
+
+    std::list<NotifyArgs> processSlot(int32_t slot) { return process(EV_ABS, ABS_MT_SLOT, slot); }
+
+    std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); }
+};
+
+// This test simulates a multi-finger gesture with unexpected reset in between. This might happen
+// due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be
+// reset, MT slot state to be re-populated and the gesture should be cancelled and restarted.
+TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) {
+    std::list<NotifyArgs> args;
+
+    // Two fingers down at once.
+    constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2;
+    int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225;
+    processKey(BTN_TOUCH, 1);
+    args += processPosition(x1, y1);
+    args += processId(FIRST_TRACKING_ID);
+    args += processSlot(1);
+    args += processPosition(x2, y2);
+    args += processId(SECOND_TRACKING_ID);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(ACTION_POINTER_1_DOWN))));
+
+    // Move.
+    x1 += 10;
+    y1 += 15;
+    x2 += 5;
+    y2 -= 10;
+    args = processSlot(0);
+    args += processPosition(x1, y1);
+    args += processSlot(1);
+    args += processPosition(x2, y2);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+    const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
+
+    // On buffer overflow mapper will be reset and MT slots data will be repopulated
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
+            .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) {
+                *outValue = 1;
+                return OK;
+            });
+
+    mockSlotValues(
+            {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
+
+    setScanCodeState(KeyState::DOWN, {BTN_TOUCH});
+
+    args = mMapper->reset(systemTime(SYSTEM_TIME_MONOTONIC));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))));
+
+    // SYN_REPORT should restart the gesture again
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(ACTION_POINTER_1_DOWN))));
+    ASSERT_EQ(std::get<NotifyMotionArgs>(args.back()).pointerCoords, pointerCoordsBeforeReset);
+
+    // Move.
+    x1 += 10;
+    y1 += 15;
+    x2 += 5;
+    y2 -= 10;
+    args = processSlot(0);
+    args += processPosition(x1, y1);
+    args += processSlot(1);
+    args += processPosition(x2, y2);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+
+    // First finger up.
+    args = processSlot(0);
+    args += processId(-1);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP))));
+
+    // Second finger up.
+    processKey(BTN_TOUCH, 0);
+    args = processSlot(1);
+    args += processId(-1);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = processSync();
+    ASSERT_THAT(args,
+                ElementsAre(
+                        VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
index 9fa6cdd..5e67506 100644
--- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
+++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
@@ -23,6 +23,11 @@
 protected:
     static constexpr size_t SLOT_COUNT = 8;
 
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        createDevice();
+    }
+
     MultiTouchMotionAccumulator mMotionAccumulator;
 
     void processMotionEvent(int32_t type, int32_t code, int32_t value) {
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 193b84d..1ac043c 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -145,15 +145,18 @@
 };
 
 TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
-    const std::vector<NotifyArgs> allArgs{NotifyInputDevicesChangedArgs{},
-                                          NotifyConfigurationChangedArgs{},
-                                          NotifyKeyArgs{},
-                                          NotifyMotionArgs{},
-                                          NotifySensorArgs{},
-                                          NotifySwitchArgs{},
-                                          NotifyDeviceResetArgs{},
-                                          NotifyPointerCaptureChangedArgs{},
-                                          NotifyVibratorStateArgs{}};
+    const std::vector<NotifyArgs>
+            allArgs{NotifyInputDevicesChangedArgs{},
+                    NotifyConfigurationChangedArgs{},
+                    KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build(),
+                    MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                            .pointer(FIRST_TOUCH_POINTER)
+                            .build(),
+                    NotifySensorArgs{},
+                    NotifySwitchArgs{},
+                    NotifyDeviceResetArgs{},
+                    NotifyPointerCaptureChangedArgs{},
+                    NotifyVibratorStateArgs{}};
 
     for (auto notifyArgs : allArgs) {
         mChoreographer.notify(notifyArgs);
@@ -1444,6 +1447,15 @@
     ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
                                                SECOND_DEVICE_ID));
     pc->assertPointerIconNotSet();
+
+    // The stylus stops hovering. This should cause the icon to be reset.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_NOT_SPECIFIED);
 }
 
 TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) {
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 8ba497a..a3e8eaf 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -18,6 +18,7 @@
 
 #include <cmath>
 #include <compare>
+#include <ios>
 
 #include <android-base/stringprintf.h>
 #include <android/input.h>
@@ -678,6 +679,24 @@
     return argPressure == pressure;
 }
 
+MATCHER_P(WithSize, size, "MotionEvent with specified size") {
+    const auto argSize = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE);
+    *result_listener << "expected size " << size << ", but got " << argSize;
+    return argSize == size;
+}
+
+MATCHER_P(WithOrientation, orientation, "MotionEvent with specified orientation") {
+    const auto argOrientation = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+    *result_listener << "expected orientation " << orientation << ", but got " << argOrientation;
+    return argOrientation == orientation;
+}
+
+MATCHER_P(WithDistance, distance, "MotionEvent with specified distance") {
+    const auto argDistance = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_DISTANCE);
+    *result_listener << "expected distance " << distance << ", but got " << argDistance;
+    return argDistance == distance;
+}
+
 MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
     const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
     const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
@@ -721,6 +740,12 @@
     return arg.buttonState == buttons;
 }
 
+MATCHER_P(WithMetaState, metaState, "InputEvent with specified meta state") {
+    *result_listener << "expected meta state 0x" << std::hex << metaState << ", but got 0x"
+                     << arg.metaState;
+    return arg.metaState == metaState;
+}
+
 MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") {
     *result_listener << "expected action button " << actionButton << ", but got "
                      << arg.actionButton;
@@ -743,4 +768,16 @@
     return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision;
 }
 
+MATCHER_P(WithPolicyFlags, policyFlags, "InputEvent with specified policy flags") {
+    *result_listener << "expected policy flags 0x" << std::hex << policyFlags << ", but got 0x"
+                     << arg.policyFlags;
+    return arg.policyFlags == static_cast<uint32_t>(policyFlags);
+}
+
+MATCHER_P(WithEdgeFlags, edgeFlags, "InputEvent with specified edge flags") {
+    *result_listener << "expected edge flags 0x" << std::hex << edgeFlags << ", but got 0x"
+                     << arg.edgeFlags;
+    return arg.edgeFlags == edgeFlags;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 8cf738c..fbafbad 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -37,6 +37,8 @@
 constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+constexpr auto HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER;
+constexpr auto HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT;
 constexpr int32_t DISPLAY_ID = 0;
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
@@ -101,12 +103,20 @@
         setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TRACKING_ID, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+        setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
         EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_))
                 .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) {
                     *outValue = 0;
                     return OK;
                 });
+        EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_))
+                .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> {
+                    return base::ResultError("Axis not supported", NAME_NOT_FOUND);
+                });
+        createDevice();
         mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 };
@@ -150,12 +160,14 @@
     setScanCodeState(KeyState::UP, {BTN_LEFT});
     args += process(EV_SYN, SYN_REPORT, 0);
     ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER))));
 
     // Liftoff
     args.clear();
@@ -217,12 +229,14 @@
     setScanCodeState(KeyState::UP, {BTN_LEFT});
     args += process(EV_SYN, SYN_REPORT, 0);
     ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER))));
 
     // Liftoff
     args.clear();
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 81c570d..7898126 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -201,6 +201,18 @@
                                   int32_t* outValue) const override {
         return mFdp->ConsumeIntegral<status_t>();
     }
+    base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
+                                                       size_t slotCount) const override {
+        if (mFdp->ConsumeBool()) {
+            std::vector<int32_t> outValues(slotCount + 1);
+            for (size_t i = 0; i < outValues.size(); i++) {
+                outValues.push_back(mFdp->ConsumeIntegral<int32_t>());
+            }
+            return std::move(outValues);
+        } else {
+            return base::ResultError("Fuzzer", UNKNOWN_ERROR);
+        }
+    }
     bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
                                uint8_t* outFlags) const override {
         return mFdp->ConsumeBool();
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 0dd4dd6..019fefa 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -15,7 +15,7 @@
 
 cc_aconfig_library {
     name: "sensorservice_flags_c_lib",
-    aconfig_declarations: "dynamic_sensors_flags",
+    aconfig_declarations: "sensorservice_flags",
     host_supported: true,
 }
 
@@ -82,6 +82,7 @@
         "android.hardware.sensors@2.1",
         "android.hardware.common-V2-ndk",
         "android.hardware.common.fmq-V1-ndk",
+        "server_configurable_flags",
     ],
 
     static_libs: [
@@ -89,6 +90,7 @@
         "android.hardware.sensors@1.0-convert",
         "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V2-ndk",
+        "sensorservice_flags_c_lib",
     ],
 
     generated_headers: ["framework-cppstream-protos"],
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index dd83fde..45fab7e 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -25,6 +25,7 @@
 
 #include <android-base/logging.h>
 #include <android/util/ProtoOutputStream.h>
+#include <com_android_frameworks_sensorservice_flags.h>
 #include <cutils/atomic.h>
 #include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
 #include <hardware/sensors-base.h>
@@ -42,6 +43,7 @@
 
 using namespace android::hardware::sensors;
 using android::util::ProtoOutputStream;
+namespace sensorservice_flags = com::android::frameworks::sensorservice::flags;
 
 namespace android {
 // ---------------------------------------------------------------------------
@@ -416,8 +418,15 @@
 }
 
 void SensorDevice::onDynamicSensorsDisconnected(
-        const std::vector<int32_t>& /* dynamicSensorHandlesRemoved */) {
-    // TODO: Currently dynamic sensors do not seem to be removed
+        const std::vector<int32_t>& dynamicSensorHandlesRemoved) {
+    if (sensorservice_flags::sensor_device_on_dynamic_sensor_disconnected()) {
+        for (auto handle : dynamicSensorHandlesRemoved) {
+            auto it = mConnectedDynamicSensors.find(handle);
+            if (it != mConnectedDynamicSensors.end()) {
+                mConnectedDynamicSensors.erase(it);
+            }
+        }
+    }
 }
 
 void SensorDevice::writeWakeLockHandled(uint32_t count) {
@@ -483,12 +492,16 @@
     } else {
         ALOGD_IF(DEBUG_CONNECTIONS, "disable index=%zd", info.batchParams.indexOfKey(ident));
 
-        // If a connected dynamic sensor is deactivated, remove it from the
-        // dictionary.
+        // TODO(b/316958439): Remove these line after
+        // sensor_device_on_dynamic_sensor_disconnected is ramped up. Bounded
+        // here since this function is coupled with
+        // dynamic_sensors_hal_disconnect_dynamic_sensor flag. If a connected
+        // dynamic sensor is deactivated, remove it from the dictionary.
         auto it = mConnectedDynamicSensors.find(handle);
         if (it != mConnectedDynamicSensors.end()) {
-            mConnectedDynamicSensors.erase(it);
+          mConnectedDynamicSensors.erase(it);
         }
+        // End of TODO(b/316958439)
 
         if (info.removeBatchParamsForIdent(ident) >= 0) {
             if (info.numActiveClients() == 0) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 30ba9e4..a7296f3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3370,7 +3370,6 @@
     static const mat4 kDefaultColorTransformMat;
 
     static const Region kDebugRegion;
-    static const compositionengine::CompositionRefreshArgs kDefaultRefreshArgs;
     static const HdrCapabilities kHdrCapabilities;
 
     StrictMock<mock::CompositionEngine> mCompositionEngine;
@@ -3393,7 +3392,6 @@
 const Rect OutputComposeSurfacesTest::kDefaultOutputViewport{1005, 1006, 1007, 1008};
 const Rect OutputComposeSurfacesTest::kDefaultOutputDestinationClip{1013, 1014, 1015, 1016};
 const mat4 OutputComposeSurfacesTest::kDefaultColorTransformMat{mat4() * 0.5f};
-const compositionengine::CompositionRefreshArgs OutputComposeSurfacesTest::kDefaultRefreshArgs;
 const Region OutputComposeSurfacesTest::kDebugRegion{Rect{100, 101, 102, 103}};
 
 const HdrCapabilities OutputComposeSurfacesTest::
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ad5e42b..bb32afa 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -1217,8 +1217,8 @@
             Rect inputBoundsInDisplaySpace =
                     getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds,
                                                  displayInfo.transform);
-            snapshot->inputInfo.touchableRegion = snapshot->inputInfo.touchableRegion.intersect(
-                    displayInfo.transform.transform(inputBoundsInDisplaySpace));
+            snapshot->inputInfo.touchableRegion =
+                    snapshot->inputInfo.touchableRegion.intersect(inputBoundsInDisplaySpace);
         }
 
         // If the layer is a clone, we need to crop the input region to cloned root to prevent
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 97fca39..9c4f7a5 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -62,6 +62,10 @@
             mLastAnimationTime = std::max(lastPresentTime, now);
             break;
         case LayerUpdateType::SetFrameRate:
+            if (FlagManager::getInstance().vrr_config()) {
+                break;
+            }
+            FALLTHROUGH_INTENDED;
         case LayerUpdateType::Buffer:
             FrameTimeData frameTime = {.presentTime = lastPresentTime,
                                        .queueTime = mLastUpdatedTime,
@@ -528,6 +532,8 @@
             return FrameRateCategory::Low;
         case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL:
             return FrameRateCategory::Normal;
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT:
+            return FrameRateCategory::HighHint;
         case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH:
             return FrameRateCategory::High;
         default:
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index e06221a..c3709e5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -420,6 +420,11 @@
     const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
 
     if (layer.vote == LayerVoteType::ExplicitCategory) {
+        // HighHint is considered later for touch boost.
+        if (layer.frameRateCategory == FrameRateCategory::HighHint) {
+            return 0.f;
+        }
+
         if (getFrameRateCategoryRange(layer.frameRateCategory).includes(refreshRate)) {
             return 1.f;
         }
@@ -507,6 +512,7 @@
     int explicitExact = 0;
     int explicitGteLayers = 0;
     int explicitCategoryVoteLayers = 0;
+    int interactiveLayers = 0;
     int seamedFocusedLayers = 0;
     int categorySmoothSwitchOnlyLayers = 0;
 
@@ -534,7 +540,13 @@
                 explicitGteLayers++;
                 break;
             case LayerVoteType::ExplicitCategory:
-                explicitCategoryVoteLayers++;
+                if (layer.frameRateCategory == FrameRateCategory::HighHint) {
+                    // HighHint does not count as an explicit signal from an app. It may be
+                    // be a touch signal.
+                    interactiveLayers++;
+                } else {
+                    explicitCategoryVoteLayers++;
+                }
                 if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
                     // Count this layer for Min vote as well. The explicit vote avoids
                     // touch boost and idle for choosing a category, while Min vote is for correct
@@ -831,13 +843,14 @@
     const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
     using fps_approx_ops::operator<;
 
-    if (signals.touch && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 &&
+    const bool hasInteraction = signals.touch || interactiveLayers > 0;
+    if (hasInteraction && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 &&
         touchBoostForExplicitExact &&
         scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
         ALOGV("Touch Boost");
         ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
                               to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
-        return {touchRefreshRates, GlobalSignals{.touch = true}};
+        return {touchRefreshRates, GlobalSignals{.touch = signals.touch}};
     }
 
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
@@ -1512,6 +1525,7 @@
             return FpsRange{60_Hz, 90_Hz};
         case FrameRateCategory::Low:
             return FpsRange{30_Hz, 30_Hz};
+        case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
             LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 28e35de..6e12b33 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -313,7 +313,7 @@
             ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold)
             : timePoint;
     const auto vsyncTime = snapToVsyncAlignedWithRenderRate(baseTime);
-    if (FlagManager::getInstance().vrr_config()) {
+    if (FlagManager::getInstance().vrr_config() && mDisplayModePtr->getVrrConfig()) {
         const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
         const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
         mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 2806450..84ef89f 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -88,6 +88,7 @@
     NoPreference,
     Low,
     Normal,
+    HighHint,
     High,
 
     ftl_last = High
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index b65a2b3..f097a13 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -98,6 +98,7 @@
   namespace: "core_graphics"
   description: "Whether to use the closest known refresh rate to determine the fps consistency."
   bug: "299201319"
+  is_fixed_read_only: true
 }
 
 # This flag is broken.
@@ -149,3 +150,19 @@
   bug: "286084594"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "vulkan_renderengine"
+  namespace: "core_graphics"
+  description: "Use Vulkan backend in RenderEngine prior to switching to Graphite."
+  bug: "293371537"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "graphite_renderengine"
+  namespace: "core_graphics"
+  description: "Use Skia's Graphite Vulkan backend in RenderEngine."
+  bug: "293371537"
+  is_fixed_read_only: true
+}
diff --git a/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
index 2c71a2e..99062f0 100644
--- a/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
@@ -179,23 +179,35 @@
         tracingSession->StopBlocking();
 
         auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
-        EXPECT_EQ(packets.size(), 1);
+        ASSERT_EQ(packets.size(), 2);
 
-        const auto& packet = packets[0];
-        ASSERT_TRUE(packet.has_timestamp());
-        EXPECT_EQ(packet.timestamp(), timestamp);
-        ASSERT_TRUE(packet.has_graphics_frame_event());
-        const auto& frame_event = packet.graphics_frame_event();
-        ASSERT_TRUE(frame_event.has_buffer_event());
-        const auto& buffer_event = frame_event.buffer_event();
-        ASSERT_TRUE(buffer_event.has_buffer_id());
-        EXPECT_EQ(buffer_event.buffer_id(), bufferID);
-        ASSERT_TRUE(buffer_event.has_frame_number());
-        EXPECT_EQ(buffer_event.frame_number(), frameNumber);
-        ASSERT_TRUE(buffer_event.has_type());
-        EXPECT_EQ(buffer_event.type(), perfetto::protos::GraphicsFrameEvent_BufferEventType(type));
-        ASSERT_TRUE(buffer_event.has_duration_ns());
-        EXPECT_EQ(buffer_event.duration_ns(), duration);
+        const auto& packet1 = packets[0];
+        ASSERT_TRUE(packet1.has_timestamp());
+        EXPECT_EQ(packet1.timestamp(), timestamp);
+        ASSERT_TRUE(packet1.has_graphics_frame_event());
+        const auto& frame_event1 = packet1.graphics_frame_event();
+        ASSERT_TRUE(frame_event1.has_buffer_event());
+        const auto& buffer_event1 = frame_event1.buffer_event();
+        ASSERT_TRUE(buffer_event1.has_buffer_id());
+        EXPECT_EQ(buffer_event1.buffer_id(), bufferID);
+        ASSERT_TRUE(buffer_event1.has_frame_number());
+        EXPECT_EQ(buffer_event1.frame_number(), frameNumber);
+        ASSERT_TRUE(buffer_event1.has_type());
+        EXPECT_EQ(buffer_event1.type(), perfetto::protos::GraphicsFrameEvent_BufferEventType(type));
+        ASSERT_TRUE(buffer_event1.has_duration_ns());
+        EXPECT_EQ(buffer_event1.duration_ns(), duration);
+
+        const auto& packet2 = packets[1];
+        ASSERT_TRUE(packet2.has_timestamp());
+        EXPECT_EQ(packet2.timestamp(), 0);
+        ASSERT_TRUE(packet2.has_graphics_frame_event());
+        const auto& frame_event2 = packet2.graphics_frame_event();
+        ASSERT_TRUE(frame_event2.has_buffer_event());
+        const auto& buffer_event2 = frame_event2.buffer_event();
+        ASSERT_TRUE(buffer_event2.has_type());
+        EXPECT_EQ(buffer_event2.type(),
+                  perfetto::protos::GraphicsFrameEvent_BufferEventType(
+                          FrameTracer::FrameEvent::UNSPECIFIED));
     }
 }
 
@@ -219,7 +231,18 @@
         tracingSession->StopBlocking();
 
         auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
-        EXPECT_EQ(packets.size(), 0);
+        ASSERT_EQ(packets.size(), 1);
+        const auto& packet = packets[0];
+        ASSERT_TRUE(packet.has_timestamp());
+        EXPECT_EQ(packet.timestamp(), 0);
+        ASSERT_TRUE(packet.has_graphics_frame_event());
+        const auto& frame_event = packet.graphics_frame_event();
+        ASSERT_TRUE(frame_event.has_buffer_event());
+        const auto& buffer_event = frame_event.buffer_event();
+        ASSERT_TRUE(buffer_event.has_type());
+        EXPECT_EQ(buffer_event.type(),
+                  perfetto::protos::GraphicsFrameEvent_BufferEventType(
+                          FrameTracer::FrameEvent::UNSPECIFIED));
     }
 
     {
@@ -235,22 +258,56 @@
         tracingSession->StopBlocking();
 
         auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
-        EXPECT_EQ(packets.size(), 2); // Two packets because of the extra trace made above.
+        ASSERT_EQ(packets.size(), 3);
 
-        const auto& packet = packets[1];
-        ASSERT_TRUE(packet.has_timestamp());
-        EXPECT_EQ(packet.timestamp(), timestamp);
-        ASSERT_TRUE(packet.has_graphics_frame_event());
-        const auto& frame_event = packet.graphics_frame_event();
-        ASSERT_TRUE(frame_event.has_buffer_event());
-        const auto& buffer_event = frame_event.buffer_event();
-        ASSERT_TRUE(buffer_event.has_buffer_id());
-        EXPECT_EQ(buffer_event.buffer_id(), bufferID);
-        ASSERT_TRUE(buffer_event.has_frame_number());
-        EXPECT_EQ(buffer_event.frame_number(), frameNumber);
-        ASSERT_TRUE(buffer_event.has_type());
-        EXPECT_EQ(buffer_event.type(), perfetto::protos::GraphicsFrameEvent_BufferEventType(type));
-        EXPECT_FALSE(buffer_event.has_duration_ns());
+        const auto& packet1 = packets[0];
+        ASSERT_TRUE(packet1.has_timestamp());
+        EXPECT_EQ(packet1.timestamp(), timestamp);
+        ASSERT_TRUE(packet1.has_graphics_frame_event());
+        const auto& frame_event1 = packet1.graphics_frame_event();
+        ASSERT_TRUE(frame_event1.has_buffer_event());
+        const auto& buffer_event1 = frame_event1.buffer_event();
+        ASSERT_TRUE(buffer_event1.has_buffer_id());
+        EXPECT_EQ(buffer_event1.buffer_id(), bufferID);
+        ASSERT_TRUE(buffer_event1.has_frame_number());
+        EXPECT_EQ(buffer_event1.frame_number(), frameNumber);
+        ASSERT_TRUE(buffer_event1.has_type());
+        EXPECT_EQ(buffer_event1.type(),
+                  perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+        EXPECT_FALSE(buffer_event1.has_duration_ns());
+
+        const auto& packet2 = packets[1];
+        ASSERT_TRUE(packet2.has_timestamp());
+        EXPECT_EQ(packet2.timestamp(), timestamp);
+        ASSERT_TRUE(packet2.has_graphics_frame_event());
+        const auto& frame_event2 = packet2.graphics_frame_event();
+        ASSERT_TRUE(frame_event2.has_buffer_event());
+        const auto& buffer_event2 = frame_event2.buffer_event();
+        ASSERT_TRUE(buffer_event2.has_buffer_id());
+        EXPECT_EQ(buffer_event2.buffer_id(), bufferID);
+        ASSERT_TRUE(buffer_event2.has_frame_number());
+        EXPECT_EQ(buffer_event2.frame_number(), frameNumber);
+        ASSERT_TRUE(buffer_event2.has_type());
+        EXPECT_EQ(buffer_event2.type(),
+                  perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+        EXPECT_FALSE(buffer_event2.has_duration_ns());
+
+        const auto& packet3 = packets[2];
+        ASSERT_TRUE(packet3.has_timestamp());
+        EXPECT_EQ(packet3.timestamp(), 0);
+        ASSERT_TRUE(packet3.has_graphics_frame_event());
+        const auto& frame_event3 = packet3.graphics_frame_event();
+        ASSERT_TRUE(frame_event3.has_buffer_event());
+        const auto& buffer_event3 = frame_event3.buffer_event();
+        ASSERT_TRUE(buffer_event3.has_buffer_id());
+        EXPECT_EQ(buffer_event3.buffer_id(), bufferID);
+        ASSERT_TRUE(buffer_event3.has_frame_number());
+        EXPECT_EQ(buffer_event3.frame_number(), 0);
+        ASSERT_TRUE(buffer_event3.has_type());
+        EXPECT_EQ(buffer_event3.type(),
+                  perfetto::protos::GraphicsFrameEvent::BufferEventType(
+                          FrameTracer::FrameEvent::UNSPECIFIED));
+        EXPECT_FALSE(buffer_event3.has_duration_ns());
     }
 }
 
@@ -285,7 +342,7 @@
     tracingSession->StopBlocking();
 
     auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
-    EXPECT_EQ(packets.size(), 2);
+    ASSERT_EQ(packets.size(), 3);
 
     const auto& packet1 = packets[0];
     ASSERT_TRUE(packet1.has_timestamp());
@@ -293,6 +350,8 @@
     ASSERT_TRUE(packet1.has_graphics_frame_event());
     ASSERT_TRUE(packet1.graphics_frame_event().has_buffer_event());
     ASSERT_FALSE(packet1.graphics_frame_event().buffer_event().has_duration_ns());
+    EXPECT_EQ(packet1.graphics_frame_event().buffer_event().type(),
+              perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
 
     const auto& packet2 = packets[1];
     ASSERT_TRUE(packet2.has_timestamp());
@@ -300,6 +359,17 @@
     ASSERT_TRUE(packet2.has_graphics_frame_event());
     ASSERT_TRUE(packet2.graphics_frame_event().has_buffer_event());
     ASSERT_FALSE(packet2.graphics_frame_event().buffer_event().has_duration_ns());
+    EXPECT_EQ(packet2.graphics_frame_event().buffer_event().type(),
+              perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+
+    const auto& packet3 = packets[2];
+    ASSERT_TRUE(packet3.has_timestamp());
+    EXPECT_EQ(packet3.timestamp(), 0);
+    ASSERT_TRUE(packet3.has_graphics_frame_event());
+    ASSERT_TRUE(packet3.graphics_frame_event().has_buffer_event());
+    EXPECT_EQ(packet3.graphics_frame_event().buffer_event().type(),
+              perfetto::protos::GraphicsFrameEvent::BufferEventType(
+                      FrameTracer::FrameEvent::UNSPECIFIED));
 }
 
 TEST_F(FrameTracerTest, traceFenceOlderThanDeadline_ShouldBeIgnored) {
@@ -322,7 +392,15 @@
     tracingSession->StopBlocking();
 
     auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
-    EXPECT_EQ(packets.size(), 0);
+    ASSERT_EQ(packets.size(), 1);
+    const auto& packet = packets[0];
+    ASSERT_TRUE(packet.has_timestamp());
+    EXPECT_EQ(packet.timestamp(), 0);
+    ASSERT_TRUE(packet.has_graphics_frame_event());
+    ASSERT_TRUE(packet.graphics_frame_event().has_buffer_event());
+    EXPECT_EQ(packet.graphics_frame_event().buffer_event().type(),
+              perfetto::protos::GraphicsFrameEvent::BufferEventType(
+                      FrameTracer::FrameEvent::UNSPECIFIED));
 }
 
 TEST_F(FrameTracerTest, traceFenceWithValidStartTime_ShouldHaveCorrectDuration) {
@@ -357,7 +435,7 @@
     tracingSession->StopBlocking();
 
     auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
-    EXPECT_EQ(packets.size(), 2);
+    ASSERT_EQ(packets.size(), 3);
 
     const auto& packet1 = packets[0];
     ASSERT_TRUE(packet1.has_timestamp());
@@ -367,6 +445,7 @@
     ASSERT_TRUE(packet1.graphics_frame_event().buffer_event().has_duration_ns());
     const auto& buffer_event1 = packet1.graphics_frame_event().buffer_event();
     EXPECT_EQ(buffer_event1.duration_ns(), duration);
+    EXPECT_EQ(buffer_event1.type(), perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
 
     const auto& packet2 = packets[1];
     ASSERT_TRUE(packet2.has_timestamp());
@@ -376,6 +455,17 @@
     ASSERT_TRUE(packet2.graphics_frame_event().buffer_event().has_duration_ns());
     const auto& buffer_event2 = packet2.graphics_frame_event().buffer_event();
     EXPECT_EQ(buffer_event2.duration_ns(), duration);
+    EXPECT_EQ(buffer_event2.type(), perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+
+    const auto& packet3 = packets[2];
+    ASSERT_TRUE(packet3.has_timestamp());
+    EXPECT_EQ(packet3.timestamp(), 0);
+    ASSERT_TRUE(packet3.has_graphics_frame_event());
+    ASSERT_TRUE(packet3.graphics_frame_event().has_buffer_event());
+    const auto& buffer_event3 = packet3.graphics_frame_event().buffer_event();
+    EXPECT_EQ(buffer_event3.type(),
+              perfetto::protos::GraphicsFrameEvent::BufferEventType(
+                      FrameTracer::FrameEvent::UNSPECIFIED));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index ba32c68..6394d63 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -353,6 +353,23 @@
     EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop);
 }
 
+TEST_F(LayerSnapshotTest, CanCropTouchableRegionWithDisplayTransform) {
+    DisplayInfo displayInfo;
+    displayInfo.transform = ui::Transform(ui::Transform::RotationFlags::ROT_90, 1000, 1000);
+    mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), displayInfo);
+
+    Rect touchCrop{300, 300, 400, 500};
+    createRootLayer(3);
+    setCrop(3, touchCrop);
+    setLayerStack(3, 1);
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegionCrop(3, touch, /*touchCropId=*/3, /*replaceTouchableRegionWithCrop=*/false);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3});
+    Rect rotatedCrop = {500, 300, 700, 400};
+    EXPECT_EQ(getSnapshot({.id = 3})->inputInfo.touchableRegion.bounds(), rotatedCrop);
+}
+
 TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) {
     int blurRadius = 42;
     setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
@@ -650,7 +667,7 @@
     // │   └── 13
     // └── 2
     setFrameRate(11, 244.f, 0, 0);
-    setFrameRateCategory(122, 3 /* Normal */);
+    setFrameRateCategory(122, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL);
 
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     // verify parent 1 gets no vote
@@ -845,7 +862,7 @@
     // │   │       └── 1221
     // │   └── 13
     // └── 2
-    setFrameRateCategory(12, 4 /* high */);
+    setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
     setFrameRate(122, 123.f, 0, 0);
     setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
 
@@ -887,7 +904,7 @@
     // │   │       └── 1221
     // │   └── 13
     // └── 2
-    setFrameRateCategory(12, 0 /* default */);
+    setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT);
     setFrameRateSelectionStrategy(12, 0 /* Default */);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     // verify parent 1 gets no vote
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 0cacf81..1e526ba 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1607,6 +1607,82 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_HighHint) {
+    auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitDefault";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    EXPECT_EQ(30_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId30, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::HighHint;
+    lr2.name = "ExplicitCategory HighHint#2";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::Low;
+    lr2.name = "ExplicitCategory Low";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    EXPECT_EQ(30_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId30, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitExact;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExact";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    if (selector.supportsAppFrameRateOverrideByContent()) {
+        // Gets touch boost
+        EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+        EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+    } else {
+        EXPECT_EQ(30_Hz, actualFrameRateMode.fps);
+        EXPECT_EQ(kModeId30, actualFrameRateMode.modePtr->getId());
+    }
+}
+
 TEST_P(RefreshRateSelectorTest,
        getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
     if (GetParam() != Config::FrameRateOverride::Enabled) {
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 961ba57..d952b70 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -32,6 +32,7 @@
 #include <gtest/gtest.h>
 #include <algorithm>
 #include <chrono>
+#include <optional>
 #include <utility>
 
 #include <com_android_graphics_surfaceflinger_flags.h>
@@ -733,6 +734,27 @@
     EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
 }
 
+TEST_F(VSyncPredictorTest, absentVrrConfigNoVsyncTrackerCallback) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
+    const std::optional<hal::VrrConfig> vrrConfigOpt = std::nullopt;
+    constexpr int32_t kGroup = 0;
+    constexpr auto kResolution = ui::Size(1920, 1080);
+    const auto mode =
+            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfigOpt,
+                                                  kGroup, kResolution, DEFAULT_DISPLAY_ID));
+    tracker.setDisplayModePtr(mode);
+
+    auto last = mNow;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_CALL(mVsyncTrackerCallback, onVsyncGenerated(_, _, _)).Times(0);
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+    }
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index 5bcce50..e298e7c 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -45,8 +45,8 @@
 }
 
 inline DisplayModePtr createVrrDisplayMode(
-        DisplayModeId modeId, Fps displayRefreshRate, hal::VrrConfig vrrConfig, int32_t group = 0,
-        ui::Size resolution = ui::Size(1920, 1080),
+        DisplayModeId modeId, Fps displayRefreshRate, std::optional<hal::VrrConfig> vrrConfig,
+        int32_t group = 0, ui::Size resolution = ui::Size(1920, 1080),
         PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
     return createDisplayModeBuilder(modeId, displayRefreshRate, group, resolution, displayId)
             .setVrrConfig(std::move(vrrConfig))
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 0e45d2d..81fd118 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -340,8 +340,9 @@
     ALOGD("Unload builtin Vulkan driver.");
 
     // Close the opened device
-    ALOG_ASSERT(!hal_.dev_->common.close(hal_.dev_->common),
-                "hw_device_t::close() failed.");
+    int err = hal_.dev_->common.close(
+        const_cast<struct hw_device_t*>(&hal_.dev_->common));
+    ALOG_ASSERT(!err, "hw_device_t::close() failed.");
 
     // Close the opened shared library in the hw_module_t
     android_unload_sphal_library(hal_.dev_->common.module->dso);
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 0df5e77..6b3c379 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1762,6 +1762,8 @@
     }
 
     int query_value;
+    // TODO: Now that we are calling into GPDSC2 directly, this query may be redundant
+    //       the call to std::max(min_buffer_count, num_images) may be redundant as well
     err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
                         &query_value);
     if (err != android::OK || query_value < 0) {
@@ -1778,12 +1780,33 @@
     // with extra images (which they can't actually use!).
     const uint32_t min_buffer_count = min_undequeued_buffers + 1;
 
-    uint32_t num_images;
-    if (create_info->presentMode  == VK_PRESENT_MODE_MAILBOX_KHR) {
-        num_images = std::max(3u, create_info->minImageCount);
-    } else {
-        num_images = create_info->minImageCount;
-    }
+    // Call into GPDSC2 to get the minimum and maximum allowable buffer count for the surface of
+    // interest. This step is only necessary if the app requests a number of images
+    // (create_info->minImageCount) that is less or more than the surface capabilities.
+    // An app should be calling GPDSC2 and using those values to set create_info, but in the
+    // event that the app has hard-coded image counts an error can occur
+    VkSurfacePresentModeEXT present_mode = {
+        VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT,
+        nullptr,
+        create_info->presentMode
+    };
+    VkPhysicalDeviceSurfaceInfo2KHR surface_info2 = {
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
+        &present_mode,
+        create_info->surface
+    };
+    VkSurfaceCapabilities2KHR surface_capabilities2 = {
+        VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
+        nullptr,
+        {},
+    };
+    result = GetPhysicalDeviceSurfaceCapabilities2KHR(GetData(device).driver_physical_device,
+            &surface_info2, &surface_capabilities2);
+
+    uint32_t num_images = create_info->minImageCount;
+    num_images = std::clamp(num_images,
+            surface_capabilities2.surfaceCapabilities.minImageCount,
+            surface_capabilities2.surfaceCapabilities.maxImageCount);
 
     const uint32_t buffer_count = std::max(min_buffer_count, num_images);
     err = native_window_set_buffer_count(window, buffer_count);