Merge "PointerChoreographer: Show spot hover icon for stylus when showing taps" into main
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 82cacca..d233902 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -145,6 +145,9 @@
  * Buffers which are replaced or removed from the scene in the transaction invoking
  * this callback may be reused after this point.
  *
+ * Starting with API level 36, prefer using \a ASurfaceTransaction_OnBufferRelease to listen
+ * to when a buffer is ready to be reused.
+ *
  * \param context Optional context provided by the client that is passed into
  * the callback.
  *
@@ -190,6 +193,35 @@
         __INTRODUCED_IN(31);
 
 /**
+ * The ASurfaceTransaction_OnBufferRelease callback is invoked when a buffer that was passed in
+ * ASurfaceTransaction_setBuffer is ready to be reused.
+ *
+ * This callback is guaranteed to be invoked if ASurfaceTransaction_setBuffer is called with a non
+ * null buffer. If the buffer in the transaction is replaced via another call to
+ * ASurfaceTransaction_setBuffer, the callback will be invoked immediately. Otherwise the callback
+ * will be invoked before the ASurfaceTransaction_OnComplete callback after the buffer was
+ * presented.
+ *
+ * If this callback is set, caller should not release the buffer using the
+ * ASurfaceTransaction_OnComplete.
+ *
+ * \param context Optional context provided by the client that is passed into the callback.
+ *
+ * \param release_fence_fd Returns the fence file descriptor used to signal the release of buffer
+ * associated with this callback. If this fence is valid (>=0), the buffer has not yet been released
+ * and the fence will signal when the buffer has been released. If the fence is -1 , the buffer is
+ * already released. The recipient of the callback takes ownership of the fence fd and is
+ * responsible for closing it.
+ *
+ * THREADING
+ * The callback can be invoked on any thread.
+ *
+ * Available since API level 36.
+ */
+typedef void (*ASurfaceTransaction_OnBufferRelease)(void* _Null_unspecified context,
+                                                    int release_fence_fd) __INTRODUCED_IN(36);
+
+/**
  * Returns the timestamp of when the frame was latched by the framework. Once a frame is
  * latched by the framework, it is presented at the following hardware vsync.
  *
@@ -251,7 +283,7 @@
 /**
  * The returns the fence used to signal the release of the PREVIOUS buffer set on
  * this surface. If this fence is valid (>=0), the PREVIOUS buffer has not yet been released and the
- * fence will signal when the PREVIOUS buffer has been released. If the fence is -1 , the PREVIOUS
+ * fence will signal when the PREVIOUS buffer has been released. If the fence is -1, the PREVIOUS
  * buffer is already released. The recipient of the callback takes ownership of the
  * previousReleaseFenceFd and is responsible for closing it.
  *
@@ -353,6 +385,9 @@
  * Note that the buffer must be allocated with AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE
  * as the surface control might be composited using the GPU.
  *
+ * Starting with API level 36, prefer using \a ASurfaceTransaction_setBufferWithRelease to
+ * set a buffer and a callback which will be invoked when the buffer is ready to be reused.
+ *
  * Available since API level 29.
  */
 void ASurfaceTransaction_setBuffer(ASurfaceTransaction* _Nonnull transaction,
@@ -361,6 +396,29 @@
         __INTRODUCED_IN(29);
 
 /**
+ * Updates the AHardwareBuffer displayed for \a surface_control. If not -1, the
+ * acquire_fence_fd should be a file descriptor that is signaled when all pending work
+ * for the buffer is complete and the buffer can be safely read.
+ *
+ * The frameworks takes ownership of the \a acquire_fence_fd passed and is responsible
+ * for closing it.
+ *
+ * Note that the buffer must be allocated with AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE
+ * as the surface control might be composited using the GPU.
+ *
+ * When the buffer is ready to be reused, the ASurfaceTransaction_OnBufferRelease
+ * callback will be invoked. If the buffer is null, the callback will not be invoked.
+ *
+ * Available since API level 36.
+ */
+void ASurfaceTransaction_setBufferWithRelease(ASurfaceTransaction* _Nonnull transaction,
+                                              ASurfaceControl* _Nonnull surface_control,
+                                              AHardwareBuffer* _Nonnull buffer,
+                                              int acquire_fence_fd, void* _Null_unspecified context,
+                                              ASurfaceTransaction_OnBufferRelease _Nonnull func)
+        __INTRODUCED_IN(36);
+
+/**
  * Updates the color for \a surface_control.  This will make the background color for the
  * ASurfaceControl visible in transparent regions of the surface.  Colors \a r, \a g,
  * and \a b must be within the range that is valid for \a dataspace.  \a dataspace and \a alpha
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
index 2892137..67d92bd 100644
--- a/include/input/Resampler.h
+++ b/include/input/Resampler.h
@@ -35,9 +35,9 @@
     virtual ~Resampler() = default;
 
     /**
-     * Tries to resample motionEvent at resampleTime. The provided resampleTime must be greater than
+     * Tries to resample motionEvent at frameTime. The provided frameTime must be greater than
      * the latest sample time of motionEvent. It is not guaranteed that resampling occurs at
-     * resampleTime. Interpolation may occur is futureSample is available. Otherwise, motionEvent
+     * frameTime. Interpolation may occur is futureSample is available. Otherwise, motionEvent
      * may be resampled by another method, or not resampled at all. Furthermore, it is the
      * implementer's responsibility to guarantee the following:
      * - If resampling occurs, a single additional sample should be added to motionEvent. That is,
@@ -45,15 +45,14 @@
      * samples by the end of the resampling. No other field of motionEvent should be modified.
      * - If resampling does not occur, then motionEvent must not be modified in any way.
      */
-    virtual void resampleMotionEvent(std::chrono::nanoseconds resampleTime,
-                                     MotionEvent& motionEvent,
+    virtual void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent,
                                      const InputMessage* futureSample) = 0;
 };
 
 class LegacyResampler final : public Resampler {
 public:
     /**
-     * Tries to resample `motionEvent` at `resampleTime` by adding a resampled sample at the end of
+     * Tries to resample `motionEvent` at `frameTime` by adding a resampled sample at the end of
      * `motionEvent` with eventTime equal to `resampleTime` and pointer coordinates determined by
      * linear interpolation or linear extrapolation. An earlier `resampleTime` will be used if
      * extrapolation takes place and `resampleTime` is too far in the future. If `futureSample` is
@@ -61,7 +60,7 @@
      * data, LegacyResampler will extrapolate. Otherwise, no resampling takes place and
      * `motionEvent` is unmodified.
      */
-    void resampleMotionEvent(std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
+    void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent,
                              const InputMessage* futureSample) override;
 
 private:
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 319716e..cbba711 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -40,6 +40,7 @@
 
 cc_library_headers {
     name: "libarect_headers",
+    host_supported: true,
     vendor_available: true,
     min_sdk_version: "29",
     // TODO(b/153609531): remove when no longer needed.
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index f518a22..3cd2b9a 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -46,6 +46,7 @@
 #include "android/binder_ibinder.h"
 
 using namespace android;
+using namespace std::chrono_literals;
 
 constexpr char kExistingNonNdkService[] = "SurfaceFlinger";
 constexpr char kBinderNdkUnitTestService[] = "BinderNdkUnitTest";
@@ -54,7 +55,7 @@
 constexpr char kActiveServicesNdkUnitTestService[] = "ActiveServicesNdkUnitTestService";
 constexpr char kBinderNdkUnitTestServiceFlagged[] = "BinderNdkUnitTestFlagged";
 
-constexpr unsigned int kShutdownWaitTime = 11;
+constexpr auto kShutdownWaitTime = 30s;
 constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
 
 class MyTestFoo : public IFoo {
@@ -253,12 +254,22 @@
 }
 
 bool isServiceRunning(const char* serviceName) {
-    AIBinder* binder = AServiceManager_checkService(serviceName);
-    if (binder == nullptr) {
-        return false;
+    static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+    const Vector<String16> services = sm->listServices();
+    for (const auto service : services) {
+        if (service == String16(serviceName)) return true;
     }
-    AIBinder_decStrong(binder);
+    return false;
+}
 
+bool isServiceShutdownWithWait(const char* serviceName) {
+    LOG(INFO) << "About to check and wait for shutdown of " << std::string(serviceName);
+    const auto before = std::chrono::steady_clock::now();
+    while (isServiceRunning(serviceName)) {
+        sleep(1);
+        const auto after = std::chrono::steady_clock::now();
+        if (after - before >= kShutdownWaitTime) return false;
+    }
     return true;
 }
 
@@ -450,8 +461,8 @@
     service = nullptr;
     IPCThreadState::self()->flushCommands();
     // Make sure the service is dead after some time of no use
-    sleep(kShutdownWaitTime);
-    ASSERT_EQ(nullptr, AServiceManager_checkService(kLazyBinderNdkUnitTestService));
+    ASSERT_TRUE(isServiceShutdownWithWait(kLazyBinderNdkUnitTestService))
+            << "Service failed to shut down";
 }
 
 TEST(NdkBinder, ForcedPersistenceTest) {
@@ -466,14 +477,12 @@
         service = nullptr;
         IPCThreadState::self()->flushCommands();
 
-        sleep(kShutdownWaitTime);
-
-        bool isRunning = isServiceRunning(kForcePersistNdkUnitTestService);
-
         if (i == 0) {
-            ASSERT_TRUE(isRunning) << "Service shut down when it shouldn't have.";
+            ASSERT_TRUE(isServiceRunning(kForcePersistNdkUnitTestService))
+                    << "Service shut down when it shouldn't have.";
         } else {
-            ASSERT_FALSE(isRunning) << "Service failed to shut down.";
+            ASSERT_TRUE(isServiceShutdownWithWait(kForcePersistNdkUnitTestService))
+                    << "Service failed to shut down";
         }
     }
 }
@@ -491,10 +500,7 @@
     service = nullptr;
     IPCThreadState::self()->flushCommands();
 
-    LOG(INFO) << "ActiveServicesCallbackTest about to sleep";
-    sleep(kShutdownWaitTime);
-
-    ASSERT_FALSE(isServiceRunning(kActiveServicesNdkUnitTestService))
+    ASSERT_TRUE(isServiceShutdownWithWait(kActiveServicesNdkUnitTestService))
             << "Service failed to shut down.";
 }
 
diff --git a/libs/binder/tests/binderCacheUnitTest.cpp b/libs/binder/tests/binderCacheUnitTest.cpp
index 92dab19..482d197 100644
--- a/libs/binder/tests/binderCacheUnitTest.cpp
+++ b/libs/binder/tests/binderCacheUnitTest.cpp
@@ -137,9 +137,9 @@
     ASSERT_EQ(binder1, result);
 
     // Kill the server, this should remove from cache.
-    foo.killServer(binder1);
     pid_t pid;
     ASSERT_EQ(OK, binder1->getDebugPid(&pid));
+    foo.killServer(binder1);
     system(("kill -9 " + std::to_string(pid)).c_str());
 
     sp<IBinder> binder2 = sp<BBinder>::make();
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index fbca35e..a1e53c5 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -454,7 +454,7 @@
         GTEST_SKIP() << "This test requires multiple threads";
     }
 
-    constexpr size_t kNumThreads = 10;
+    constexpr size_t kNumThreads = 5;
 
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
 
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 94998e5..c65eafa 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -1266,6 +1266,11 @@
     mTransactionHangCallback = std::move(callback);
 }
 
+void BLASTBufferQueue::setApplyToken(sp<IBinder> applyToken) {
+    std::lock_guard _lock{mMutex};
+    mApplyToken = std::move(applyToken);
+}
+
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
 
 BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 868dbd0..ba58a15 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -143,7 +143,7 @@
      * indicates the reason for the hang.
      */
     void setTransactionHangCallback(std::function<void(const std::string&)> callback);
-
+    void setApplyToken(sp<IBinder>);
     virtual ~BLASTBufferQueue();
 
     void onFirstRef() override;
@@ -272,7 +272,7 @@
 
     // Queues up transactions using this token in SurfaceFlinger. This prevents queued up
     // transactions from other parts of the client from blocking this transaction.
-    const sp<IBinder> mApplyToken GUARDED_BY(mMutex) = sp<BBinder>::make();
+    sp<IBinder> mApplyToken GUARDED_BY(mMutex) = sp<BBinder>::make();
 
     // Guards access to mDequeueTimestamps since we cannot hold to mMutex in onFrameDequeued or
     // we will deadlock.
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index eb2a61d..53f4a36 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -186,6 +186,10 @@
         mBlastBufferQueueAdapter->mergeWithNextTransaction(merge, frameNumber);
     }
 
+    void setApplyToken(sp<IBinder> applyToken) {
+        mBlastBufferQueueAdapter->setApplyToken(std::move(applyToken));
+    }
+
 private:
     sp<TestBLASTBufferQueue> mBlastBufferQueueAdapter;
 };
@@ -511,6 +515,69 @@
     adapter.waitForCallbacks();
 }
 
+class WaitForCommittedCallback {
+public:
+    WaitForCommittedCallback() = default;
+    ~WaitForCommittedCallback() = default;
+
+    void wait() {
+        std::unique_lock lock(mMutex);
+        cv.wait(lock, [this] { return mCallbackReceived; });
+    }
+
+    void notify() {
+        std::unique_lock lock(mMutex);
+        mCallbackReceived = true;
+        cv.notify_one();
+        mCallbackReceivedTimeStamp = std::chrono::system_clock::now();
+    }
+    auto getCallback() {
+        return [this](void* /* unused context */, nsecs_t /* latchTime */,
+                      const sp<Fence>& /* presentFence */,
+                      const std::vector<SurfaceControlStats>& /* stats */) { notify(); };
+    }
+    std::chrono::time_point<std::chrono::system_clock> mCallbackReceivedTimeStamp;
+
+private:
+    std::mutex mMutex;
+    std::condition_variable cv;
+    bool mCallbackReceived = false;
+};
+
+TEST_F(BLASTBufferQueueTest, setApplyToken) {
+    sp<IBinder> applyToken = sp<BBinder>::make();
+    WaitForCommittedCallback firstTransaction;
+    WaitForCommittedCallback secondTransaction;
+    {
+        BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+        adapter.setApplyToken(applyToken);
+        sp<IGraphicBufferProducer> igbProducer;
+        setUpProducer(adapter, igbProducer);
+
+        Transaction t;
+        t.addTransactionCommittedCallback(firstTransaction.getCallback(), nullptr);
+        adapter.mergeWithNextTransaction(&t, 1);
+        queueBuffer(igbProducer, 127, 127, 127,
+                    /*presentTimeDelay*/ std::chrono::nanoseconds(500ms).count());
+    }
+    {
+        BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+        adapter.setApplyToken(applyToken);
+        sp<IGraphicBufferProducer> igbProducer;
+        setUpProducer(adapter, igbProducer);
+
+        Transaction t;
+        t.addTransactionCommittedCallback(secondTransaction.getCallback(), nullptr);
+        adapter.mergeWithNextTransaction(&t, 1);
+        queueBuffer(igbProducer, 127, 127, 127, /*presentTimeDelay*/ 0);
+    }
+
+    firstTransaction.wait();
+    secondTransaction.wait();
+    EXPECT_GT(secondTransaction.mCallbackReceivedTimeStamp,
+              firstTransaction.mCallbackReceivedTimeStamp);
+}
+
 TEST_F(BLASTBufferQueueTest, SetCrop_Item) {
     uint8_t r = 255;
     uint8_t g = 0;
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index c663649..b535ff4 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -241,13 +241,15 @@
                           motionEvent.getId());
 }
 
-void LegacyResampler::resampleMotionEvent(nanoseconds resampleTime, MotionEvent& motionEvent,
+void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
                                           const InputMessage* futureSample) {
     if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
         mLatestSamples.clear();
     }
     mPreviousDeviceId = motionEvent.getDeviceId();
 
+    const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
+
     updateLatestSamples(motionEvent);
 
     const std::optional<Sample> sample = (futureSample != nullptr)
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 43bc894..3ec167a 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -95,6 +95,7 @@
             },
         },
     },
+    native_coverage: false,
 }
 
 // NOTE: This is a compile time test, and does not need to be
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
index 7ae9a28..26dee39 100644
--- a/libs/input/tests/Resampler_test.cpp
+++ b/libs/input/tests/Resampler_test.cpp
@@ -120,6 +120,47 @@
 
 } // namespace
 
+/**
+ * The testing setup assumes an input rate of 200 Hz and a display rate of 60 Hz. This implies that
+ * input events are received every 5 milliseconds, while the display consumes batched events every
+ * ~16 milliseconds. The resampler's RESAMPLE_LATENCY constant determines the resample time, which
+ * is calculated as frameTime - RESAMPLE_LATENCY. resampleTime specifies the time used for
+ * resampling. For example, if the desired frame time consumption is ~16 milliseconds, the resample
+ * time would be ~11 milliseconds. Consequenly, the last added sample to the motion event has an
+ * event time of ~11 milliseconds. Note that there are specific scenarios where resampleMotionEvent
+ * is not called with a multiple of ~16 milliseconds. These cases are primarily for data addition
+ * or to test other functionalities of the resampler.
+ *
+ * Coordinates are calculated using linear interpolation (lerp) based on the last two available
+ * samples. Linear interpolation is defined as (a + alpha*(b - a)). Let t_b and t_a be the
+ * timestamps of samples a and b, respectively. The interpolation factor alpha is calculated as
+ * (resampleTime - t_a) / (t_b - t_a). The value of alpha determines whether the resampled
+ * coordinates are interpolated or extrapolated. If alpha falls within the semi-closed interval [0,
+ * 1), the coordinates are interpolated. If alpha is greater than or equal to 1, the coordinates are
+ * extrapolated.
+ *
+ * The timeline below depics an interpolation scenario
+ * -----------------------------------|---------|---------|---------|----------
+ *                                   10ms      11ms      15ms      16ms
+ *                                   MOVE       |        MOVE       |
+ *                                         resampleTime         frameTime
+ * Based on the timeline alpha is (11 - 10)/(15 - 10) = 1/5. Thus, coordinates are interpolated.
+ *
+ * The following timeline portrays an extrapolation scenario
+ * -------------------------|---------|---------|-------------------|----------
+ *                          5ms      10ms      11ms                16ms
+ *                          MOVE     MOVE       |                   |
+ *                                         resampleTime         frameTime
+ * Likewise, alpha = (11 - 5)/(10 - 5) = 6/5. Hence, coordinates are extrapolated.
+ *
+ * If a motion event was resampled, the tests will check that the following conditions are satisfied
+ * to guarantee resampling correctness:
+ * - The motion event metadata must not change.
+ * - The number of samples in the motion event must only increment by 1.
+ * - The resampled values must be at the end of motion event coordinates.
+ * - The rasamples values must be near the hand calculations.
+ * - The resampled time must be the most recent one in motion event.
+ */
 class ResamplerTest : public testing::Test {
 protected:
     ResamplerTest() : mResampler(std::make_unique<LegacyResampler>()) {}
@@ -225,7 +266,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     EXPECT_EQ(motionEvent.getTouchMajor(0), TOUCH_MAJOR_VALUE);
 
@@ -243,7 +284,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -270,23 +311,6 @@
     assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
 }
 
-// Increments of 16 ms for display refresh rate
-// Increments of 6 ms for input frequency
-// Resampling latency is known to be 5 ms
-// Therefore, first resampling time will be 11 ms
-
-/**
- * Timeline
- * ----+----------------------+---------+---------+---------+----------
- *     0ms                   10ms      11ms      15ms      16ms
- *    DOWN                   MOVE       |        MSG        |
- *                                  resample              frame
- * Resampling occurs at 11ms. It is possible to interpolate because there is a sample available
- * after the resample time. It is assumed that the InputMessage frequency is 100Hz, and the frame
- * frequency is 60Hz. This means the time between InputMessage samples is 10ms, and the time between
- * frames is ~16ms. Resample time is frameTime - RESAMPLE_LATENCY. The resampled sample must be the
- * last one in the batch to consume.
- */
 TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
     MotionEvent motionEvent =
             InputStream{{InputSample{10ms,
@@ -297,7 +321,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -338,18 +362,13 @@
 
     const MotionEvent originalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
                                               {Pointer{.id = 0,
                                                        .x = 2.2f,
                                                        .y = 4.4f,
                                                        .isResampled = true}});
-    // Integrity of the whole motionEvent
-    // History size should increment by 1
-    // Check if the resampled value is the last one
-    // Check if the resampleTime is correct
-    // Check if the PointerCoords are consistent with the other computations
 }
 
 TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
@@ -364,7 +383,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -382,7 +401,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -400,7 +419,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -414,7 +433,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(27ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(32ms, motionEvent, nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -428,7 +447,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(43ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(48ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -451,7 +470,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 2.2f, .y = 2.2f, .isResampled = true},
@@ -475,7 +494,7 @@
 
     const MotionEvent originalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -498,7 +517,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -517,7 +536,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -539,7 +558,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 1.4f, .y = 1.4f, .isResampled = true},
@@ -560,7 +579,7 @@
 
     const MotionEvent originalSecondMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(27ms, secondMotionEvent, &secondFutureSample);
+    mResampler->resampleMotionEvent(32ms, secondMotionEvent, &secondFutureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalSecondMotionEvent, secondMotionEvent,
                                               {Pointer{.x = 3.8f, .y = 3.8f, .isResampled = true},
@@ -586,7 +605,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -606,7 +625,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -629,7 +648,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -650,7 +669,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -672,7 +691,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -691,7 +710,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -713,7 +732,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -746,7 +765,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -782,7 +801,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -815,7 +834,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -847,7 +866,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
diff --git a/services/gpuservice/vts/TEST_MAPPING b/services/gpuservice/vts/TEST_MAPPING
index b33e962..a809be1 100644
--- a/services/gpuservice/vts/TEST_MAPPING
+++ b/services/gpuservice/vts/TEST_MAPPING
@@ -1,7 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "GpuServiceVendorTests"
+      "name": "GpuServiceVendorTests",
+      "options": [
+        {
+          // Exclude test methods that require a physical device to run.
+          "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+        }
+      ]
     }
   ]
 }
diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
index 6c16335..5c12323 100644
--- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
+++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
@@ -19,7 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
-import android.platform.test.annotations.RestrictedBuildTest;
+import android.platform.test.annotations.RequiresDevice;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -63,7 +63,7 @@
     }
 
     @VsrTest(requirements={"VSR-3.3-004"})
-    @RestrictedBuildTest
+    @RequiresDevice
     @Test
     public void testGpuWorkPeriodTracepointFormat() throws Exception {
         CommandResult commandResult = getDevice().executeShellV2Command(
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index af9d2eb..10fec74 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -298,9 +298,14 @@
       "name": "CtsInputRootTestCases"
     }
   ],
-  "staged-platinum-postsubmit": [
+  "platinum-postsubmit": [
     {
       "name": "inputflinger_tests"
     }
+  ],
+  "staged-platinum-postsubmit": [
+    {
+      "name": "libinput_tests"
+    }
   ]
 }
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 7bec94e..69874c9 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -466,6 +466,9 @@
     virtual void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                              int32_t deviceId) = 0;
 
+    /* Sends the Info of gestures that happen on the touchpad. */
+    virtual void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) = 0;
+
     /* Gets the keyboard layout for a particular input device. */
     virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index c8e7790..dd46bbc 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -20,14 +20,19 @@
 #include <sstream>
 
 #include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 namespace {
 
+static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+
 int32_t actionWithIndex(int32_t action, int32_t index) {
     return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 }
@@ -43,6 +48,12 @@
     return i;
 }
 
+void addRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+                       RawAbsoluteAxisInfo& evdevAxis) {
+    deviceInfo.addMotionRange(androidAxis, SOURCE, evdevAxis.minValue, evdevAxis.maxValue,
+                              evdevAxis.flat, evdevAxis.fuzz, evdevAxis.resolution);
+}
+
 } // namespace
 
 CapturedTouchpadEventConverter::CapturedTouchpadEventConverter(
@@ -108,8 +119,15 @@
 }
 
 void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
-    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
-    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+    if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+        tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_X,
+                                         AMOTION_EVENT_AXIS_RELATIVE_X, ABS_MT_POSITION_X);
+        tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_Y,
+                                         AMOTION_EVENT_AXIS_RELATIVE_Y, ABS_MT_POSITION_Y);
+    } else {
+        tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
+        tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+    }
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
@@ -135,8 +153,23 @@
                                                           int32_t evdevAxis) const {
     std::optional<RawAbsoluteAxisInfo> info = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
     if (info) {
-        deviceInfo.addMotionRange(androidAxis, SOURCE, info->minValue, info->maxValue, info->flat,
-                                  info->fuzz, info->resolution);
+        addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *info);
+    }
+}
+
+void CapturedTouchpadEventConverter::tryAddRawMotionRangeWithRelative(InputDeviceInfo& deviceInfo,
+                                                                      int32_t androidAxis,
+                                                                      int32_t androidRelativeAxis,
+                                                                      int32_t evdevAxis) const {
+    std::optional<RawAbsoluteAxisInfo> axisInfo = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
+    if (axisInfo) {
+        addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *axisInfo);
+
+        // The largest movement we could possibly report on a relative axis is from the minimum to
+        // the maximum (or vice versa) of the absolute axis.
+        float range = axisInfo->maxValue - axisInfo->minValue;
+        deviceInfo.addMotionRange(androidRelativeAxis, SOURCE, -range, range, axisInfo->flat,
+                                  axisInfo->fuzz, axisInfo->resolution);
     }
 }
 
@@ -163,7 +196,7 @@
     std::list<NotifyArgs> out;
     std::vector<PointerCoords> coords;
     std::vector<PointerProperties> properties;
-    std::map<size_t, size_t> coordsIndexForSlotNumber;
+    std::map<size_t /*slotNumber*/, size_t /*coordsIndex*/> coordsIndexForSlotNumber;
 
     // For all the touches that were already down, send a MOVE event with their updated coordinates.
     // A convention of the MotionEvent API is that pointer coordinates in UP events match the
@@ -175,11 +208,19 @@
             // to stay perfectly still between frames, and if it does the worst that can happen is
             // an extra MOVE event, so it's not worth the overhead of checking for changes.
             coordsIndexForSlotNumber[slotNumber] = coords.size();
-            coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+            coords.push_back(makePointerCoordsForSlot(slotNumber));
             properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
         }
         out.push_back(
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
+        if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+            // For any further events we send from this sync, the pointers won't have moved relative
+            // to the positions we just reported in this MOVE event, so zero out the relative axes.
+            for (PointerCoords& pointer : coords) {
+                pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+                pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+            }
+        }
     }
 
     std::vector<size_t> upSlots, downSlots;
@@ -234,6 +275,9 @@
                                      /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0));
 
         freePointerIdForSlot(slotNumber);
+        if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+            mPreviousCoordsForSlotNumber.erase(slotNumber);
+        }
         coords.erase(coords.begin() + indexToRemove);
         properties.erase(properties.begin() + indexToRemove);
         // Now that we've removed some coords and properties, we might have to update the slot
@@ -254,7 +298,7 @@
                 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
 
         coordsIndexForSlotNumber[slotNumber] = coordsIndex;
-        coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+        coords.push_back(makePointerCoordsForSlot(slotNumber));
         properties.push_back(
                 {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
 
@@ -286,12 +330,22 @@
                             AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
 }
 
-PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
-        const MultiTouchMotionAccumulator::Slot& slot) const {
+PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(size_t slotNumber) {
+    const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(slotNumber);
     PointerCoords coords;
     coords.clear();
     coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
+    if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+        if (auto it = mPreviousCoordsForSlotNumber.find(slotNumber);
+            it != mPreviousCoordsForSlotNumber.end()) {
+            auto [oldX, oldY] = it->second;
+            coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, slot.getX() - oldX);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, slot.getY() - oldY);
+        }
+        mPreviousCoordsForSlotNumber[slotNumber] = std::make_pair(slot.getX(), slot.getY());
+    }
+
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
index 9b6df7a..d6c0708 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
@@ -21,6 +21,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <android/input.h>
@@ -49,12 +50,14 @@
 private:
     void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
                               int32_t evdevAxis) const;
+    void tryAddRawMotionRangeWithRelative(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+                                          int32_t androidRelativeAxis, int32_t evdevAxis) const;
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   const std::vector<PointerCoords>& coords,
                                                   const std::vector<PointerProperties>& properties,
                                                   int32_t actionButton = 0, int32_t flags = 0);
-    PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const;
+    PointerCoords makePointerCoordsForSlot(size_t slotNumber);
     int32_t allocatePointerIdToSlot(size_t slotNumber);
     void freePointerIdForSlot(size_t slotNumber);
 
@@ -76,8 +79,7 @@
 
     std::bitset<MAX_POINTER_ID + 1> mPointerIdsInUse;
     std::map<size_t, int32_t> mPointerIdForSlotNumber;
-
-    static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+    std::map<size_t, std::pair<float, float>> mPreviousCoordsForSlotNumber;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index b17e79a..9a36bfb 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -480,6 +480,9 @@
         return;
     }
     mGesturesToProcess.push_back(*gesture);
+    if (mTouchpadHardwareStateNotificationsEnabled) {
+        getPolicy()->notifyTouchpadGestureInfo(gesture->type, getDeviceId());
+    }
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 95283ba..744cf4a 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -123,4 +123,5 @@
         "device-tests",
         "device-platinum-tests",
     ],
+    native_coverage: false,
 }
diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
index f20c43c..353011a 100644
--- a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
+++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
@@ -20,6 +20,7 @@
 #include <memory>
 
 #include <EventHub.h>
+#include <com_android_input_flags.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
 #include <linux/input.h>
@@ -32,6 +33,8 @@
 #include "TestEventMatchers.h"
 #include "TestInputListener.h"
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 using testing::AllOf;
@@ -47,6 +50,8 @@
             mReader(mFakeEventHub, mFakePolicy, mFakeListener),
             mDevice(newDevice()),
             mDeviceContext(*mDevice, EVENTHUB_ID) {
+        input_flags::include_relative_axis_values_for_captured_touchpads(true);
+
         const size_t slotCount = 8;
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0);
         mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true);
@@ -126,7 +131,7 @@
 
 TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_allAxesPresent_populatedCorrectly) {
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 2000, 0, 0, 40);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1100, 0, 0, 35);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 30);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 900, 0, 0, 25);
@@ -150,8 +155,8 @@
     const InputDeviceInfo::MotionRange* posY =
             info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, posY);
-    EXPECT_NEAR(0, posY->min, EPSILON);
-    EXPECT_NEAR(2500, posY->max, EPSILON);
+    EXPECT_NEAR(-500, posY->min, EPSILON);
+    EXPECT_NEAR(2000, posY->max, EPSILON);
     EXPECT_NEAR(40, posY->resolution, EPSILON);
 
     const InputDeviceInfo::MotionRange* touchMajor =
@@ -182,8 +187,22 @@
     EXPECT_NEAR(800, toolMinor->max, EPSILON);
     EXPECT_NEAR(20, toolMinor->resolution, EPSILON);
 
-    // ...except orientation and pressure, which get scaled, and size, which is generated from other
-    // values.
+    // ...except for the relative motion axes, derived from the corresponding absolute ones:
+    const InputDeviceInfo::MotionRange* relX =
+            info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, relX);
+    EXPECT_NEAR(-4000, relX->min, EPSILON);
+    EXPECT_NEAR(4000, relX->max, EPSILON);
+    EXPECT_NEAR(45, relX->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* relY =
+            info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, relY);
+    EXPECT_NEAR(-2500, relY->min, EPSILON);
+    EXPECT_NEAR(2500, relY->max, EPSILON);
+    EXPECT_NEAR(40, relY->resolution, EPSILON);
+
+    // ...orientation and pressure, which get scaled:
     const InputDeviceInfo::MotionRange* orientation =
             info.getMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, orientation);
@@ -198,6 +217,7 @@
     EXPECT_NEAR(1, pressure->max, EPSILON);
     EXPECT_NEAR(0, pressure->resolution, EPSILON);
 
+    // ... and size, which is generated from other values.
     const InputDeviceInfo::MotionRange* size =
             info.getMotionRange(AMOTION_EVENT_AXIS_SIZE, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, size);
@@ -219,7 +239,9 @@
     // present, since it's generated from axes that aren't provided by this device).
     EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD));
     EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD));
-    EXPECT_EQ(2u, info.getMotionRanges().size());
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_EQ(4u, info.getMotionRanges().size());
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, OneFinger_motionReportedCorrectly) {
@@ -235,14 +257,16 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+                      WithCoords(50, 100), WithRelativeMotion(0, 0),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99);
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+                      WithCoords(52, 99), WithRelativeMotion(2, -1),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
     processAxis(conv, EV_KEY, BTN_TOUCH, 0);
@@ -255,8 +279,9 @@
                             VariantWith<NotifyMotionArgs>(
                                     WithMotionAction(AMOTION_EVENT_ACTION_UP))));
     EXPECT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(52, 99), WithPointerCount(1u),
-                                                         WithToolType(ToolType::FINGER)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(52, 99), WithRelativeMotion(0, 0), WithPointerCount(1u),
+                              WithToolType(ToolType::FINGER)))));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, OneFinger_touchDimensionsPassedThrough) {
@@ -507,13 +532,13 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(51, 100)));
+                      WithCoords(51, 100), WithRelativeMotion(0, 0)));
 
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 100)));
+                      WithCoords(52, 100), WithRelativeMotion(1, 0)));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, FingerArrivingAfterPalm_onlyFingerReported) {
@@ -553,7 +578,7 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(98, 148)));
+                      WithCoords(98, 148), WithRelativeMotion(-2, -2)));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, FingerAndFingerTurningIntoPalm_partiallyCancelled) {
@@ -660,7 +685,8 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+                      WithCoords(50, 100), WithRelativeMotion(0, 0),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
@@ -678,13 +704,16 @@
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                                           WithPointerCount(1u), WithCoords(52, 99),
+                                          WithRelativeMotion(2, -1),
                                           WithToolType(ToolType::FINGER))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(
                                                   AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                           WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+                                          WithPointerRelativeMotion(0, 0, 0),
                                           WithPointerCoords(1, 250, 200),
+                                          WithPointerRelativeMotion(1, 0, 0),
                                           WithPointerToolType(0, ToolType::FINGER),
                                           WithPointerToolType(1, ToolType::FINGER)))));
 
@@ -700,14 +729,17 @@
     std::list<NotifyArgs> args = processSync(conv);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(
-                                    AMOTION_EVENT_ACTION_POINTER_UP |
-                                    0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT))));
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithPointerRelativeMotion(1, 5, 2))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerRelativeMotion(1, 0, 0)))));
     EXPECT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithPointerCount(2u), WithPointerCoords(0, 52, 99),
-                              WithPointerCoords(1, 255, 202),
+                              WithPointerRelativeMotion(0, 0, 0), WithPointerCoords(1, 255, 202),
                               WithPointerToolType(1, ToolType::FINGER),
                               WithPointerToolType(0, ToolType::FINGER)))));
 
@@ -723,9 +755,69 @@
                                     WithMotionAction(AMOTION_EVENT_ACTION_UP))));
     EXPECT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(AllOf(WithPointerCount(1u), WithCoords(255, 202),
+                                                         WithRelativeMotion(0, 0),
                                                          WithToolType(ToolType::FINGER)))));
 }
 
+TEST_F(CapturedTouchpadEventConverterTest, RelativeMotionAxesClearedForNewFingerInSlot) {
+    CapturedTouchpadEventConverter conv = createConverter();
+    // Put down one finger.
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(50, 100), WithRelativeMotion(0, 0)));
+
+    // Move it in negative X and Y directions.
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 47);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 97);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(47, 97),
+                      WithRelativeMotion(-3, -3)));
+
+    // Lift it.
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(47, 97),
+                                                         WithRelativeMotion(0, 0),
+                                                         WithPointerCount(1u)))));
+
+    // Put down another finger using the same slot. Relative axis values should be cleared.
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 60);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 60);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(60, 60), WithRelativeMotion(0, 0)));
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 64);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 58);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(64, 58), WithRelativeMotion(4, -2)));
+}
+
 // Pointer IDs max out at 31, and so must be reused once a touch is lifted to avoid running out.
 TEST_F(CapturedTouchpadEventConverterTest, PointerIdsReusedAfterLift) {
     CapturedTouchpadEventConverter conv = createConverter();
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index e1f844c..f373cac 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -256,6 +256,10 @@
     mTouchpadHardwareStateNotified.notify_all();
 }
 
+void FakeInputReaderPolicy::notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) {
+    std::scoped_lock lock(mLock);
+}
+
 std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
         const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) {
     return nullptr;
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 61bb9fc..3a2b4e9 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -85,6 +85,7 @@
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
     void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                      int32_t deviceId) override;
+    void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
     std::string getDeviceAlias(const InputDeviceIdentifier&) override;
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index cfedc6e..6fa3365 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -615,7 +615,12 @@
     explicit WithPointerIdMatcher(size_t index, int32_t pointerId)
           : mIndex(index), mPointerId(pointerId) {}
 
-    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream* os) const {
+        if (mIndex >= args.pointerCoords.size()) {
+            *os << "Pointer index " << mIndex << " is out of bounds";
+            return false;
+        }
+
         return args.pointerProperties[mIndex].id == mPointerId;
     }
 
@@ -646,12 +651,51 @@
     return (isnan(x) ? isnan(argX) : x == argX) && (isnan(y) ? isnan(argY) : y == argY);
 }
 
-MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
-    const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX
-                     << ", " << argY << ")";
-    return argX == x && argY == y;
+/// Relative motion matcher
+class WithRelativeMotionMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithRelativeMotionMatcher(size_t pointerIndex, float relX, float relY)
+          : mPointerIndex(pointerIndex), mRelX(relX), mRelY(relY) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        if (mPointerIndex >= event.pointerCoords.size()) {
+            *os << "Pointer index " << mPointerIndex << " is out of bounds";
+            return false;
+        }
+
+        const PointerCoords& coords = event.pointerCoords[mPointerIndex];
+        bool matches = mRelX == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X) &&
+                mRelY == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        if (!matches) {
+            *os << "expected relative motion (" << mRelX << ", " << mRelY << ") at pointer index "
+                << mPointerIndex << ", but got ("
+                << coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X) << ", "
+                << coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y) << ")";
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with relative motion (" << mRelX << ", " << mRelY << ") at pointer index "
+            << mPointerIndex;
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong relative motion"; }
+
+private:
+    const size_t mPointerIndex;
+    const float mRelX;
+    const float mRelY;
+};
+
+inline WithRelativeMotionMatcher WithRelativeMotion(float relX, float relY) {
+    return WithRelativeMotionMatcher(0, relX, relY);
+}
+
+inline WithRelativeMotionMatcher WithPointerRelativeMotion(size_t pointerIndex, float relX,
+                                                           float relY) {
+    return WithRelativeMotionMatcher(pointerIndex, relX, relY);
 }
 
 MATCHER_P3(WithGestureOffset, dx, dy, epsilon,
@@ -758,10 +802,14 @@
     return argToolType == toolType;
 }
 
-MATCHER_P2(WithPointerToolType, pointer, toolType,
+MATCHER_P2(WithPointerToolType, pointerIndex, toolType,
            "InputEvent with specified tool type for pointer") {
-    const auto argToolType = arg.pointerProperties[pointer].toolType;
-    *result_listener << "expected pointer " << pointer << " to have tool type "
+    if (std::cmp_greater_equal(pointerIndex, arg.getPointerCount())) {
+        *result_listener << "Pointer index " << pointerIndex << " is out of bounds";
+        return false;
+    }
+    const auto argToolType = arg.pointerProperties[pointerIndex].toolType;
+    *result_listener << "expected pointer " << pointerIndex << " to have tool type "
                      << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType);
     return argToolType == toolType;
 }
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index ddc3310..7e362f4 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -283,6 +283,7 @@
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
     void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                      int32_t deviceId) override {}
+    void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) override {}
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> layoutInfo) override {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index bd093f5..d08e261 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -305,7 +305,7 @@
     // The logic here checks if hwc was able to provide some dpi, and if so if the dpi
     // disparity between the axes is more reasonable than a rough estimate, otherwise use
     // the estimated dpi as a corrected value.
-    if (estimatedDpi.x == -1 || estimatedDpi.x == -1) {
+    if (estimatedDpi.x == -1 || estimatedDpi.y == -1) {
         return dpi;
     }
     if (dpi.x == -1 || dpi.y == -1) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index be00079..5e13154 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -38,7 +38,6 @@
 #include <FrameTimeline/FrameTimeline.h>
 #include <scheduler/interface/ICompositor.h>
 
-#include <algorithm>
 #include <cinttypes>
 #include <cstdint>
 #include <functional>
@@ -46,16 +45,15 @@
 #include <numeric>
 
 #include <common/FlagManager.h>
-#include "../Layer.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "FrontEnd/LayerHandle.h"
+#include "Layer.h"
 #include "OneShotTimer.h"
 #include "RefreshRateStats.h"
 #include "SurfaceFlingerFactory.h"
 #include "SurfaceFlingerProperties.h"
 #include "TimeStats/TimeStats.h"
-#include "VSyncTracker.h"
 #include "VsyncConfiguration.h"
 #include "VsyncController.h"
 #include "VsyncSchedule.h"
@@ -361,10 +359,8 @@
 
     if (cycle == Cycle::Render) {
         mRenderEventThread = std::move(eventThread);
-        mRenderEventConnection = mRenderEventThread->createEventConnection();
     } else {
         mLastCompositeEventThread = std::move(eventThread);
-        mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 1367ec3..c88b563 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -145,10 +145,6 @@
             Cycle, EventRegistrationFlags eventRegistration = {},
             const sp<IBinder>& layerHandle = nullptr) EXCLUDES(mChoreographerLock);
 
-    const sp<EventThreadConnection>& getEventConnection(Cycle cycle) const {
-        return cycle == Cycle::Render ? mRenderEventConnection : mLastCompositeEventConnection;
-    }
-
     enum class Hotplug { Connected, Disconnected };
     void dispatchHotplug(PhysicalDisplayId, Hotplug);
 
@@ -467,10 +463,7 @@
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
 
     std::unique_ptr<EventThread> mRenderEventThread;
-    sp<EventThreadConnection> mRenderEventConnection;
-
     std::unique_ptr<EventThread> mLastCompositeEventThread;
-    sp<EventThreadConnection> mLastCompositeEventConnection;
 
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index fa377e9..3c5f68c 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -21,7 +21,6 @@
 
 #include "VsyncModulator.h"
 
-#include <android-base/properties.h>
 #include <common/trace.h>
 #include <log/log.h>
 
@@ -37,8 +36,7 @@
 
 VsyncModulator::VsyncModulator(const VsyncConfigSet& config, Now now)
       : mVsyncConfigSet(config),
-        mNow(now),
-        mTraceDetailedInfo(base::GetBoolProperty("debug.sf.vsync_trace_detailed_info", false)) {}
+        mNow(now) {}
 
 VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) {
     std::lock_guard<std::mutex> lock(mMutex);
@@ -71,10 +69,6 @@
             break;
     }
 
-    if (mTraceDetailedInfo) {
-        SFTRACE_INT("mEarlyWakeup", static_cast<int>(mEarlyWakeupRequests.size()));
-    }
-
     if (mEarlyWakeupRequests.empty() && schedule == Schedule::EarlyEnd) {
         mEarlyTransactionFrames = MIN_EARLY_TRANSACTION_FRAMES;
         mEarlyTransactionStartTime = mNow();
@@ -167,15 +161,19 @@
     const VsyncConfig& offsets = getNextVsyncConfig();
     mVsyncConfig = offsets;
 
-    if (mTraceDetailedInfo) {
-        const bool isEarly = &offsets == &mVsyncConfigSet.early;
-        const bool isEarlyGpu = &offsets == &mVsyncConfigSet.earlyGpu;
-        const bool isLate = &offsets == &mVsyncConfigSet.late;
+    // Trace config type
+    SFTRACE_INT("Vsync-Early",  &mVsyncConfig == &mVsyncConfigSet.early);
+    SFTRACE_INT("Vsync-EarlyGpu", &mVsyncConfig == &mVsyncConfigSet.earlyGpu);
+    SFTRACE_INT("Vsync-Late", &mVsyncConfig == &mVsyncConfigSet.late);
 
-        SFTRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
-        SFTRACE_INT("Vsync-EarlyGpuOffsetsOn", isEarlyGpu);
-        SFTRACE_INT("Vsync-LateOffsetsOn", isLate);
-    }
+    // Trace early vsync conditions
+    SFTRACE_INT("EarlyWakeupRequests",
+                                 static_cast<int>(mEarlyWakeupRequests.size()));
+    SFTRACE_INT("EarlyTransactionFrames", mEarlyTransactionFrames);
+    SFTRACE_INT("RefreshRateChangePending", mRefreshRateChangePending);
+
+    // Trace early gpu conditions
+    SFTRACE_INT("EarlyGpuFrames", mEarlyGpuFrames);
 
     return offsets;
 }
@@ -183,7 +181,6 @@
 void VsyncModulator::binderDied(const wp<IBinder>& who) {
     std::lock_guard<std::mutex> lock(mMutex);
     mEarlyWakeupRequests.erase(who);
-
     static_cast<void>(updateVsyncConfigLocked());
 }
 
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index be0d334..d0a7935 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -105,7 +105,6 @@
     std::atomic<TimePoint> mLastTransactionCommitTime = TimePoint();
 
     const Now mNow;
-    const bool mTraceDetailedInfo;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index fa31643..9b10c94 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -64,17 +64,6 @@
 
 void DisplayTransactionTest::injectMockScheduler(PhysicalDisplayId displayId) {
     LOG_ALWAYS_FATAL_IF(mFlinger.scheduler());
-
-    EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
-            .WillOnce(Return(
-                    sp<EventThreadConnection>::make(mEventThread, mock::EventThread::kCallingUid)));
-
-    EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(mSFEventThread,
-                                                             mock::EventThread::kCallingUid)));
-
     mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
                             std::make_shared<mock::VSyncTracker>(),
                             std::unique_ptr<EventThread>(mEventThread),
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 45ca7e2..ac09cbc 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -124,7 +124,7 @@
 
     // createConnection call to scheduler makes a createEventConnection call to EventThread. Make
     // sure that call gets executed and returns an EventThread::Connection object.
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+    EXPECT_CALL(*mEventThread, createEventConnection(_))
             .WillRepeatedly(Return(mEventThreadConnection));
 
     mScheduler->setEventThread(Cycle::Render, std::move(eventThread));
@@ -797,7 +797,7 @@
 
     const auto mockConnection1 = sp<MockEventThreadConnection>::make(mEventThread);
     const auto mockConnection2 = sp<MockEventThreadConnection>::make(mEventThread);
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+    EXPECT_CALL(*mEventThread, createEventConnection(_))
             .WillOnce(Return(mockConnection1))
             .WillOnce(Return(mockConnection2));
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 4b0a7c3..8699621 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -187,16 +187,6 @@
     mAppEventThread = eventThread.get();
     auto sfEventThread = std::make_unique<mock::EventThread>();
 
-    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                             mock::EventThread::kCallingUid)));
-
-    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                             mock::EventThread::kCallingUid)));
-
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index df16b2e..d92f891 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -74,10 +74,8 @@
     void setEventThread(Cycle cycle, std::unique_ptr<EventThread> eventThreadPtr) {
         if (cycle == Cycle::Render) {
             mRenderEventThread = std::move(eventThreadPtr);
-            mRenderEventConnection = mRenderEventThread->createEventConnection();
         } else {
             mLastCompositeEventThread = std::move(eventThreadPtr);
-            mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
         }
     }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 725354b..347a396 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <algorithm>
 #include <chrono>
 #include <memory>
 #include <variant>
@@ -44,7 +43,6 @@
 #include "Layer.h"
 #include "NativeWindowSurface.h"
 #include "RenderArea.h"
-#include "Scheduler/MessageQueue.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "SurfaceFlinger.h"
 #include "TestableScheduler.h"
@@ -60,7 +58,6 @@
 
 #include "Scheduler/VSyncTracker.h"
 #include "Scheduler/VsyncController.h"
-#include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
@@ -88,9 +85,7 @@
 public:
     ~Factory() = default;
 
-    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override {
-        return nullptr;
-    }
+    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override { return nullptr; }
 
     std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps /*currentRefreshRate*/) override {
@@ -276,17 +271,6 @@
 
         auto eventThread = makeMock<mock::EventThread>(options.useNiceMock);
         auto sfEventThread = makeMock<mock::EventThread>(options.useNiceMock);
-
-        EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-        EXPECT_CALL(*eventThread, createEventConnection(_, _))
-                .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                                 mock::EventThread::kCallingUid)));
-
-        EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-        EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-                .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                                 mock::EventThread::kCallingUid)));
-
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
 
@@ -502,7 +486,7 @@
     }
 
     auto getDisplayNativePrimaries(const sp<IBinder>& displayToken,
-                                   ui::DisplayPrimaries &primaries) {
+                                   ui::DisplayPrimaries& primaries) {
         return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries);
     }
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 8dd1a34..7398cbe 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -24,21 +24,11 @@
 
 class EventThread : public android::EventThread {
 public:
-    static constexpr auto kCallingUid = static_cast<uid_t>(0);
-
     EventThread();
     ~EventThread() override;
 
-    // TODO(b/302035909): workaround otherwise gtest complains about
-    //  error: no viable conversion from
-    //  'tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration> &&>' to 'const
-    //  tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration>>'
-    sp<EventThreadConnection> createEventConnection(EventRegistrationFlags flags) const override {
-        return createEventConnection(false, flags);
-    }
-    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (bool, EventRegistrationFlags),
-                (const));
-
+    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (EventRegistrationFlags),
+                (const, override));
     MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));