Use sequence numbers to synchronize enabling Pointer Capture (1/2)

InputReader only processes configuration change from its main thread.
This means that if there is more than one Pointer Capture change
request when the thread is busy or sleeping, it will only process the
latest one. To ensure requests to enable Pointer Capture are synchronized
with Dispatcher, we must use sequence numbers.

Requests to enable Pointer Capture have a sequence number. Requests to
disable Pointer Capture do not have a value.

Bug: 195312888
Test: atest inputflinger_tests
Test: manual with GeforceNow app, see bug.
Change-Id: I6ae9c5498dc2f783b4c7211fa3665d42e29d2919
diff --git a/include/input/Input.h b/include/input/Input.h
index 2e42409..d2d9fd4 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -1025,6 +1025,25 @@
     std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEventPool;
 };
 
+/*
+ * Describes a unique request to enable or disable Pointer Capture.
+ */
+struct PointerCaptureRequest {
+public:
+    inline PointerCaptureRequest() : enable(false), seq(0) {}
+    inline PointerCaptureRequest(bool enable, uint32_t seq) : enable(enable), seq(seq) {}
+    inline bool operator==(const PointerCaptureRequest& other) const {
+        return enable == other.enable && seq == other.seq;
+    }
+    explicit inline operator bool() const { return enable; }
+
+    // True iff this is a request to enable Pointer Capture.
+    bool enable;
+
+    // The sequence number for the request.
+    uint32_t seq;
+};
+
 } // namespace android
 
 #endif // _LIBINPUT_INPUT_H
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 33b3e1e..71b0f5f 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -287,16 +287,16 @@
 
 // --- NotifyPointerCaptureChangedArgs ---
 
-NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime,
-                                                                 bool enabled)
-      : NotifyArgs(id, eventTime), enabled(enabled) {}
+NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs(
+        int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request)
+      : NotifyArgs(id, eventTime), request(request) {}
 
 NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs(
         const NotifyPointerCaptureChangedArgs& other)
-      : NotifyArgs(other.id, other.eventTime), enabled(other.enabled) {}
+      : NotifyArgs(other.id, other.eventTime), request(other.request) {}
 
 bool NotifyPointerCaptureChangedArgs::operator==(const NotifyPointerCaptureChangedArgs& rhs) const {
-    return id == rhs.id && eventTime == rhs.eventTime && enabled == rhs.enabled;
+    return id == rhs.id && eventTime == rhs.eventTime && request == rhs.request;
 }
 
 void NotifyPointerCaptureChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index f26a9a9..05ef489 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -67,6 +67,9 @@
     if (changes & CHANGE_EXTERNAL_STYLUS_PRESENCE) {
         result += "EXTERNAL_STYLUS_PRESENCE | ";
     }
+    if (changes & CHANGE_POINTER_CAPTURE) {
+        result += "POINTER_CAPTURE | ";
+    }
     if (changes & CHANGE_ENABLED_STATE) {
         result += "ENABLED_STATE | ";
     }
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 5e9facf..6ce0313 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -116,7 +116,7 @@
 
     void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
 
-    void setPointerCapture(bool enabled) override {}
+    void setPointerCapture(const PointerCaptureRequest&) override {}
 
     void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
 
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 288068f..9744920 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -119,15 +119,15 @@
 // PointerCaptureChanged notifications always go to apps, so set the flag POLICY_FLAG_PASS_TO_USER
 // for all entries.
 PointerCaptureChangedEntry::PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime,
-                                                       bool hasPointerCapture)
+                                                       const PointerCaptureRequest& request)
       : EventEntry(id, Type::POINTER_CAPTURE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
-        pointerCaptureEnabled(hasPointerCapture) {}
+        pointerCaptureRequest(request) {}
 
 PointerCaptureChangedEntry::~PointerCaptureChangedEntry() {}
 
 std::string PointerCaptureChangedEntry::getDescription() const {
     return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
-                        pointerCaptureEnabled ? "true" : "false");
+                        pointerCaptureRequest.enable ? "true" : "false");
 }
 
 // --- DragEntry ---
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 8feb982..44c35c1 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -104,9 +104,9 @@
 };
 
 struct PointerCaptureChangedEntry : EventEntry {
-    bool pointerCaptureEnabled;
+    const PointerCaptureRequest pointerCaptureRequest;
 
-    PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, bool hasPointerCapture);
+    PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&);
     std::string getDescription() const override;
 
     ~PointerCaptureChangedEntry() override;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 59cb419..e097773 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -559,7 +559,6 @@
         mInTouchMode(true),
         mMaximumObscuringOpacityForTouch(1.0f),
         mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
-        mFocusedWindowRequestedPointerCapture(false),
         mWindowTokenWithPointerCapture(nullptr),
         mLatencyAggregator(),
         mLatencyTracker(&mLatencyAggregator),
@@ -1331,36 +1330,51 @@
 void InputDispatcher::dispatchPointerCaptureChangedLocked(
         nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
         DropReason& dropReason) {
+    dropReason = DropReason::NOT_DROPPED;
+
     const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr;
-    if (entry->pointerCaptureEnabled && haveWindowWithPointerCapture) {
-        LOG_ALWAYS_FATAL("Pointer Capture has already been enabled for the window.");
-    }
-    if (!entry->pointerCaptureEnabled && !haveWindowWithPointerCapture) {
-        // Pointer capture was already forcefully disabled because of focus change.
-        dropReason = DropReason::NOT_DROPPED;
-        return;
-    }
-
-    // Set drop reason for early returns
-    dropReason = DropReason::NO_POINTER_CAPTURE;
-
     sp<IBinder> token;
-    if (entry->pointerCaptureEnabled) {
-        // Enable Pointer Capture
-        if (!mFocusedWindowRequestedPointerCapture) {
+
+    if (entry->pointerCaptureRequest.enable) {
+        // Enable Pointer Capture.
+        if (haveWindowWithPointerCapture &&
+            (entry->pointerCaptureRequest == mCurrentPointerCaptureRequest)) {
+            LOG_ALWAYS_FATAL("This request to enable Pointer Capture has already been dispatched "
+                             "to the window.");
+        }
+        if (!mCurrentPointerCaptureRequest.enable) {
             // This can happen if a window requests capture and immediately releases capture.
             ALOGW("No window requested Pointer Capture.");
+            dropReason = DropReason::NO_POINTER_CAPTURE;
             return;
         }
+        if (entry->pointerCaptureRequest.seq != mCurrentPointerCaptureRequest.seq) {
+            ALOGI("Skipping dispatch of Pointer Capture being enabled: sequence number mismatch.");
+            return;
+        }
+
         token = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
         LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture.");
         mWindowTokenWithPointerCapture = token;
     } else {
-        // Disable Pointer Capture
+        // Disable Pointer Capture.
+        // We do not check if the sequence number matches for requests to disable Pointer Capture
+        // for two reasons:
+        //  1. Pointer Capture can be disabled by a focus change, which means we can get two entries
+        //     to disable capture with the same sequence number: one generated by
+        //     disablePointerCaptureForcedLocked() and another as an acknowledgement of Pointer
+        //     Capture being disabled in InputReader.
+        //  2. We respect any request to disable Pointer Capture generated by InputReader, since the
+        //     actual Pointer Capture state that affects events being generated by input devices is
+        //     in InputReader.
+        if (!haveWindowWithPointerCapture) {
+            // Pointer capture was already forcefully disabled because of focus change.
+            dropReason = DropReason::NOT_DROPPED;
+            return;
+        }
         token = mWindowTokenWithPointerCapture;
         mWindowTokenWithPointerCapture = nullptr;
-        if (mFocusedWindowRequestedPointerCapture) {
-            mFocusedWindowRequestedPointerCapture = false;
+        if (mCurrentPointerCaptureRequest.enable) {
             setPointerCaptureLocked(false);
         }
     }
@@ -1369,8 +1383,7 @@
     if (channel == nullptr) {
         // Window has gone away, clean up Pointer Capture state.
         mWindowTokenWithPointerCapture = nullptr;
-        if (mFocusedWindowRequestedPointerCapture) {
-            mFocusedWindowRequestedPointerCapture = false;
+        if (mCurrentPointerCaptureRequest.enable) {
             setPointerCaptureLocked(false);
         }
         return;
@@ -3183,7 +3196,7 @@
                         static_cast<const PointerCaptureChangedEntry&>(eventEntry);
                 status = connection->inputPublisher
                                  .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
-                                                      captureEntry.pointerCaptureEnabled);
+                                                      captureEntry.pointerCaptureRequest.enable);
                 break;
             }
 
@@ -3999,14 +4012,14 @@
 void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
     if (DEBUG_INBOUND_EVENT_DETAILS) {
         ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime,
-              args->enabled ? "true" : "false");
+              args->request.enable ? "true" : "false");
     }
 
     bool needWake;
     { // acquire lock
         std::scoped_lock _l(mLock);
         auto entry = std::make_unique<PointerCaptureChangedEntry>(args->id, args->eventTime,
-                                                                  args->enabled);
+                                                                  args->request);
         needWake = enqueueInboundEventLocked(std::move(entry));
     } // release lock
 
@@ -4944,8 +4957,8 @@
 std::string InputDispatcher::dumpPointerCaptureStateLocked() {
     std::string dump;
 
-    dump += StringPrintf(INDENT "FocusedWindowRequestedPointerCapture: %s\n",
-                         toString(mFocusedWindowRequestedPointerCapture));
+    dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n",
+                         toString(mCurrentPointerCaptureRequest.enable));
 
     std::string windowName = "None";
     if (mWindowTokenWithPointerCapture) {
@@ -4954,7 +4967,7 @@
         windowName = captureWindowHandle ? captureWindowHandle->getName().c_str()
                                          : "token has capture without window";
     }
-    dump += StringPrintf(INDENT "CurrentWindowWithPointerCapture: %s\n", windowName.c_str());
+    dump += StringPrintf(INDENT "Current Window with Pointer Capture: %s\n", windowName.c_str());
 
     return dump;
 }
@@ -5420,14 +5433,13 @@
             return;
         }
 
-        if (enabled == mFocusedWindowRequestedPointerCapture) {
+        if (enabled == mCurrentPointerCaptureRequest.enable) {
             ALOGW("Ignoring request to %s Pointer Capture: "
                   "window has %s requested pointer capture.",
                   enabled ? "enable" : "disable", enabled ? "already" : "not");
             return;
         }
 
-        mFocusedWindowRequestedPointerCapture = enabled;
         setPointerCaptureLocked(enabled);
     } // release lock
 
@@ -6048,14 +6060,13 @@
 }
 
 void InputDispatcher::disablePointerCaptureForcedLocked() {
-    if (!mFocusedWindowRequestedPointerCapture && !mWindowTokenWithPointerCapture) {
+    if (!mCurrentPointerCaptureRequest.enable && !mWindowTokenWithPointerCapture) {
         return;
     }
 
     ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
 
-    if (mFocusedWindowRequestedPointerCapture) {
-        mFocusedWindowRequestedPointerCapture = false;
+    if (mCurrentPointerCaptureRequest.enable) {
         setPointerCaptureLocked(false);
     }
 
@@ -6072,14 +6083,16 @@
     }
 
     auto entry = std::make_unique<PointerCaptureChangedEntry>(mIdGenerator.nextId(), now(),
-                                                              false /* hasCapture */);
+                                                              mCurrentPointerCaptureRequest);
     mInboundQueue.push_front(std::move(entry));
 }
 
-void InputDispatcher::setPointerCaptureLocked(bool enabled) {
-    auto command = [this, enabled]() REQUIRES(mLock) {
+void InputDispatcher::setPointerCaptureLocked(bool enable) {
+    mCurrentPointerCaptureRequest.enable = enable;
+    mCurrentPointerCaptureRequest.seq++;
+    auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
-        mPolicy->setPointerCapture(enabled);
+        mPolicy->setPointerCapture(request);
     };
     postCommandLocked(std::move(command));
 }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 6df333a..d0d58a4 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -383,10 +383,12 @@
 
     // Keeps track of the focused window per display and determines focus changes.
     FocusResolver mFocusResolver GUARDED_BY(mLock);
-    // Whether the focused window on the focused display has requested Pointer Capture.
-    // The state of this variable should always be in sync with the state of Pointer Capture in the
-    // policy, which is updated through setPointerCaptureLocked(enabled).
-    bool mFocusedWindowRequestedPointerCapture GUARDED_BY(mLock);
+
+    // The enabled state of this request is true iff the focused window on the focused display has
+    // requested Pointer Capture. This request also contains the sequence number associated with the
+    // current request. The state of this variable should always be in sync with the state of
+    // Pointer Capture in the policy, and is only updated through setPointerCaptureLocked(request).
+    PointerCaptureRequest mCurrentPointerCaptureRequest GUARDED_BY(mLock);
 
     // The window token that has Pointer Capture.
     // This should be in sync with PointerCaptureChangedEvents dispatched to the input channel.
@@ -396,7 +398,7 @@
     void disablePointerCaptureForcedLocked() REQUIRES(mLock);
 
     // Set the Pointer Capture state in the Policy.
-    void setPointerCaptureLocked(bool enabled) REQUIRES(mLock);
+    void setPointerCaptureLocked(bool enable) REQUIRES(mLock);
 
     // Dispatcher state at time of last ANR.
     std::string mLastAnrState GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index ebfcbe1..3c1e637 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -156,7 +156,7 @@
      *
      * InputDispatcher is solely responsible for updating the Pointer Capture state.
      */
-    virtual void setPointerCapture(bool enabled) = 0;
+    virtual void setPointerCapture(const PointerCaptureRequest&) = 0;
 
     /* Notifies the policy that the drag window has moved over to another window */
     virtual void notifyDropWindow(const sp<IBinder>& token, float x, float y) = 0;
diff --git a/services/inputflinger/docs/pointer_capture.md b/services/inputflinger/docs/pointer_capture.md
index 8da699d..0b44187 100644
--- a/services/inputflinger/docs/pointer_capture.md
+++ b/services/inputflinger/docs/pointer_capture.md
@@ -17,6 +17,8 @@
 
 `InputDispatcher` is responsible for controlling the state of Pointer Capture. Since the feature requires changes to how events are generated, Pointer Capture is configured in `InputReader`.
 
+We use a sequence number to synchronize different requests to enable Pointer Capture between InputReader and InputDispatcher.
+
 ### Enabling Pointer Capture
 
 There are four key steps that take place when Pointer Capture is enabled:
@@ -40,5 +42,5 @@
 
 `InputDispatcher` tracks two pieces of state information regarding Pointer Capture:
 
-- `mFocusedWindowRequestedPointerCapture`: Whether or not the focused window has requested Pointer Capture. This is updated whenever the Dispatcher receives requests from `InputManagerService`.
+- `mCurrentPointerCaptureRequest`: The sequence number of the current Pointer Capture request. This request is enabled iff the focused window has requested Pointer Capture. This is updated whenever the Dispatcher receives requests from `InputManagerService`.
 - `mWindowTokenWithPointerCapture`: The Binder token of the `InputWindow` that currently has Pointer Capture. This is only updated during the dispatch cycle. If it is not `nullptr`, it signifies that the window was notified that it has Pointer Capture.
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index 4b7d26d..fe74214 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -211,11 +211,12 @@
 
 /* Describes a change in the state of Pointer Capture. */
 struct NotifyPointerCaptureChangedArgs : public NotifyArgs {
-    bool enabled;
+    // The sequence number of the Pointer Capture request, if enabled.
+    PointerCaptureRequest request;
 
     inline NotifyPointerCaptureChangedArgs() {}
 
-    NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, bool enabled);
+    NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&);
 
     NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other);
 
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 7fdbbfd..3c8ac1c 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -279,29 +279,30 @@
     // True to show the location of touches on the touch screen as spots.
     bool showTouches;
 
-    // True if pointer capture is enabled.
-    bool pointerCapture;
+    // The latest request to enable or disable Pointer Capture.
+    PointerCaptureRequest pointerCaptureRequest;
 
     // The set of currently disabled input devices.
     std::set<int32_t> disabledDevices;
 
-    InputReaderConfiguration() :
-            virtualKeyQuietTime(0),
+    InputReaderConfiguration()
+          : virtualKeyQuietTime(0),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f),
             wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f),
             pointerGesturesEnabled(true),
-            pointerGestureQuietInterval(100 * 1000000LL), // 100 ms
-            pointerGestureDragMinSwitchSpeed(50), // 50 pixels per second
-            pointerGestureTapInterval(150 * 1000000LL), // 150 ms
-            pointerGestureTapDragInterval(150 * 1000000LL), // 150 ms
-            pointerGestureTapSlop(10.0f), // 10 pixels
+            pointerGestureQuietInterval(100 * 1000000LL),            // 100 ms
+            pointerGestureDragMinSwitchSpeed(50),                    // 50 pixels per second
+            pointerGestureTapInterval(150 * 1000000LL),              // 150 ms
+            pointerGestureTapDragInterval(150 * 1000000LL),          // 150 ms
+            pointerGestureTapSlop(10.0f),                            // 10 pixels
             pointerGestureMultitouchSettleInterval(100 * 1000000LL), // 100 ms
-            pointerGestureMultitouchMinDistance(15), // 15 pixels
-            pointerGestureSwipeTransitionAngleCosine(0.2588f), // cosine of 75 degrees
+            pointerGestureMultitouchMinDistance(15),                 // 15 pixels
+            pointerGestureSwipeTransitionAngleCosine(0.2588f),       // cosine of 75 degrees
             pointerGestureSwipeMaxWidthRatio(0.25f),
             pointerGestureMovementSpeedRatio(0.8f),
             pointerGestureZoomSpeedRatio(0.3f),
-            showTouches(false), pointerCapture(false) { }
+            showTouches(false),
+            pointerCaptureRequest() {}
 
     static std::string changesToString(uint32_t changes);
 
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index e71a9e9..86d4c26 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -367,9 +367,15 @@
     }
 
     if (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE) {
-        const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now,
-                                                   mConfig.pointerCapture);
-        mQueuedListener->notifyPointerCaptureChanged(&args);
+        if (mCurrentPointerCaptureRequest == mConfig.pointerCaptureRequest) {
+            ALOGV("Skipping notifying pointer capture changes: "
+                  "There was no change in the pointer capture state.");
+        } else {
+            mCurrentPointerCaptureRequest = mConfig.pointerCaptureRequest;
+            const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now,
+                                                       mCurrentPointerCaptureRequest);
+            mQueuedListener->notifyPointerCaptureChanged(&args);
+        }
     }
 }
 
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index a00c5af..e44aa0f 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -230,6 +230,8 @@
     uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock);
     void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock);
 
+    PointerCaptureRequest mCurrentPointerCaptureRequest GUARDED_BY(mLock);
+
     // state queries
     typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
     int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 437902a..2ac41b1 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -154,9 +154,9 @@
         mHWheelScale = 1.0f;
     }
 
-    if ((!changes && config->pointerCapture) ||
+    if ((!changes && config->pointerCaptureRequest.enable) ||
         (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)) {
-        if (config->pointerCapture) {
+        if (config->pointerCaptureRequest.enable) {
             if (mParameters.mode == Parameters::MODE_POINTER) {
                 mParameters.mode = Parameters::MODE_POINTER_RELATIVE;
                 mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index a9f0247..4cc8f90 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -603,7 +603,7 @@
 
     // Determine device mode.
     if (mParameters.deviceType == Parameters::DeviceType::POINTER &&
-        mConfig.pointerGesturesEnabled && !mConfig.pointerCapture) {
+        mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.enable) {
         mSource = AINPUT_SOURCE_MOUSE;
         mDeviceMode = DeviceMode::POINTER;
         if (hasStylus()) {
@@ -776,11 +776,12 @@
     // preserve the cursor position.
     if (mDeviceMode == DeviceMode::POINTER ||
         (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
-        (mParameters.deviceType == Parameters::DeviceType::POINTER && mConfig.pointerCapture)) {
+        (mParameters.deviceType == Parameters::DeviceType::POINTER &&
+         mConfig.pointerCaptureRequest.enable)) {
         if (mPointerController == nullptr) {
             mPointerController = getContext()->getPointerController(getDeviceId());
         }
-        if (mConfig.pointerCapture) {
+        if (mConfig.pointerCaptureRequest.enable) {
             mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
         }
     } else {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 0fc4708..f997cb7 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -243,19 +243,22 @@
         mConfig.keyRepeatDelay = delay;
     }
 
-    void waitForSetPointerCapture(bool enabled) {
+    PointerCaptureRequest assertSetPointerCaptureCalled(bool enabled) {
         std::unique_lock lock(mLock);
         base::ScopedLockAssertion assumeLocked(mLock);
 
         if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms,
                                                       [this, enabled]() REQUIRES(mLock) {
-                                                          return mPointerCaptureEnabled &&
-                                                                  *mPointerCaptureEnabled ==
+                                                          return mPointerCaptureRequest->enable ==
                                                                   enabled;
                                                       })) {
-            FAIL() << "Timed out waiting for setPointerCapture(" << enabled << ") to be called.";
+            ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << enabled
+                          << ") to be called.";
+            return {};
         }
-        mPointerCaptureEnabled.reset();
+        auto request = *mPointerCaptureRequest;
+        mPointerCaptureRequest.reset();
+        return request;
     }
 
     void assertSetPointerCaptureNotCalled() {
@@ -263,11 +266,11 @@
         base::ScopedLockAssertion assumeLocked(mLock);
 
         if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
-            FAIL() << "Expected setPointerCapture(enabled) to not be called, but was called. "
+            FAIL() << "Expected setPointerCapture(request) to not be called, but was called. "
                       "enabled = "
-                   << *mPointerCaptureEnabled;
+                   << std::to_string(mPointerCaptureRequest->enable);
         }
-        mPointerCaptureEnabled.reset();
+        mPointerCaptureRequest.reset();
     }
 
     void assertDropTargetEquals(const sp<IBinder>& targetToken) {
@@ -285,7 +288,8 @@
     std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
 
     std::condition_variable mPointerCaptureChangedCondition;
-    std::optional<bool> mPointerCaptureEnabled GUARDED_BY(mLock);
+
+    std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock);
 
     // ANR handling
     std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
@@ -402,9 +406,9 @@
         mOnPointerDownToken = newToken;
     }
 
-    void setPointerCapture(bool enabled) override {
+    void setPointerCapture(const PointerCaptureRequest& request) override {
         std::scoped_lock lock(mLock);
-        mPointerCaptureEnabled = {enabled};
+        mPointerCaptureRequest = {request};
         mPointerCaptureChangedCondition.notify_all();
     }
 
@@ -1385,8 +1389,9 @@
     return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
 }
 
-static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(bool enabled) {
-    return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), enabled);
+static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(
+        const PointerCaptureRequest& request) {
+    return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), request);
 }
 
 TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
@@ -4596,16 +4601,18 @@
         mWindow->consumeFocusEvent(true);
     }
 
-    void notifyPointerCaptureChanged(bool enabled) {
-        const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(enabled);
+    void notifyPointerCaptureChanged(const PointerCaptureRequest& request) {
+        const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(request);
         mDispatcher->notifyPointerCaptureChanged(&args);
     }
 
-    void requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window, bool enabled) {
+    PointerCaptureRequest requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window,
+                                                         bool enabled) {
         mDispatcher->requestPointerCapture(window->getToken(), enabled);
-        mFakePolicy->waitForSetPointerCapture(enabled);
-        notifyPointerCaptureChanged(enabled);
+        auto request = mFakePolicy->assertSetPointerCaptureCalled(enabled);
+        notifyPointerCaptureChanged(request);
         window->consumeCaptureEvent(enabled);
+        return request;
     }
 };
 
@@ -4627,7 +4634,7 @@
 }
 
 TEST_F(InputDispatcherPointerCaptureTests, DisablesPointerCaptureAfterWindowLosesFocus) {
-    requestAndVerifyPointerCapture(mWindow, true);
+    auto request = requestAndVerifyPointerCapture(mWindow, true);
 
     setFocusedWindow(mSecondWindow);
 
@@ -4635,26 +4642,26 @@
     mWindow->consumeCaptureEvent(false);
     mWindow->consumeFocusEvent(false);
     mSecondWindow->consumeFocusEvent(true);
-    mFakePolicy->waitForSetPointerCapture(false);
+    mFakePolicy->assertSetPointerCaptureCalled(false);
 
     // Ensure that additional state changes from InputReader are not sent to the window.
-    notifyPointerCaptureChanged(false);
-    notifyPointerCaptureChanged(true);
-    notifyPointerCaptureChanged(false);
+    notifyPointerCaptureChanged({});
+    notifyPointerCaptureChanged(request);
+    notifyPointerCaptureChanged({});
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
     mFakePolicy->assertSetPointerCaptureNotCalled();
 }
 
 TEST_F(InputDispatcherPointerCaptureTests, UnexpectedStateChangeDisablesPointerCapture) {
-    requestAndVerifyPointerCapture(mWindow, true);
+    auto request = requestAndVerifyPointerCapture(mWindow, true);
 
     // InputReader unexpectedly disables and enables pointer capture.
-    notifyPointerCaptureChanged(false);
-    notifyPointerCaptureChanged(true);
+    notifyPointerCaptureChanged({});
+    notifyPointerCaptureChanged(request);
 
     // Ensure that Pointer Capture is disabled.
-    mFakePolicy->waitForSetPointerCapture(false);
+    mFakePolicy->assertSetPointerCaptureCalled(false);
     mWindow->consumeCaptureEvent(false);
     mWindow->assertNoEvents();
 }
@@ -4664,24 +4671,43 @@
 
     // The first window loses focus.
     setFocusedWindow(mSecondWindow);
-    mFakePolicy->waitForSetPointerCapture(false);
+    mFakePolicy->assertSetPointerCaptureCalled(false);
     mWindow->consumeCaptureEvent(false);
 
     // Request Pointer Capture from the second window before the notification from InputReader
     // arrives.
     mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
-    mFakePolicy->waitForSetPointerCapture(true);
+    auto request = mFakePolicy->assertSetPointerCaptureCalled(true);
 
     // InputReader notifies Pointer Capture was disabled (because of the focus change).
-    notifyPointerCaptureChanged(false);
+    notifyPointerCaptureChanged({});
 
     // InputReader notifies Pointer Capture was enabled (because of mSecondWindow's request).
-    notifyPointerCaptureChanged(true);
+    notifyPointerCaptureChanged(request);
 
     mSecondWindow->consumeFocusEvent(true);
     mSecondWindow->consumeCaptureEvent(true);
 }
 
+TEST_F(InputDispatcherPointerCaptureTests, EnableRequestFollowsSequenceNumbers) {
+    // App repeatedly enables and disables capture.
+    mDispatcher->requestPointerCapture(mWindow->getToken(), true);
+    auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+    mDispatcher->requestPointerCapture(mWindow->getToken(), false);
+    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mDispatcher->requestPointerCapture(mWindow->getToken(), true);
+    auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+
+    // InputReader notifies that PointerCapture has been enabled for the first request. Since the
+    // first request is now stale, this should do nothing.
+    notifyPointerCaptureChanged(firstRequest);
+    mWindow->assertNoEvents();
+
+    // InputReader notifies that the second request was enabled.
+    notifyPointerCaptureChanged(secondRequest);
+    mWindow->consumeCaptureEvent(true);
+}
+
 class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest {
 protected:
     constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index c76ed38..91d246a 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -312,8 +312,9 @@
         transform = t;
     }
 
-    void setPointerCapture(bool enabled) {
-        mConfig.pointerCapture = enabled;
+    PointerCaptureRequest setPointerCapture(bool enabled) {
+        mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
+        return mConfig.pointerCaptureRequest;
     }
 
     void setShowTouches(bool enabled) {
@@ -327,6 +328,8 @@
     float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; }
 
 private:
+    uint32_t mNextPointerCaptureSequenceNumber = 0;
+
     DisplayViewport createDisplayViewport(int32_t displayId, int32_t width, int32_t height,
                                           int32_t orientation, bool isActive,
                                           const std::string& uniqueId,
@@ -1993,24 +1996,24 @@
 TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) {
     NotifyPointerCaptureChangedArgs args;
 
-    mFakePolicy->setPointerCapture(true);
+    auto request = mFakePolicy->setPointerCapture(true);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
-    ASSERT_TRUE(args.enabled) << "Pointer Capture should be enabled.";
+    ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled.";
+    ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match.";
 
     mFakePolicy->setPointerCapture(false);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
-    ASSERT_FALSE(args.enabled) << "Pointer Capture should be disabled.";
+    ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled.";
 
-    // Verify that the Pointer Capture state is re-configured correctly when the configuration value
+    // Verify that the Pointer Capture state is not updated when the configuration value
     // does not change.
     mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
     mReader->loopOnce();
-    mFakeListener->assertNotifyCaptureWasCalled(&args);
-    ASSERT_FALSE(args.enabled) << "Pointer Capture should be disabled.";
+    mFakeListener->assertNotifyCaptureWasNotCalled();
 }
 
 class FakeVibratorInputMapper : public FakeInputMapper {
diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index fb7de97..6a26c63 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -100,6 +100,11 @@
                                                           "to have been called."));
 }
 
+void TestInputListener::assertNotifyCaptureWasNotCalled() {
+    ASSERT_NO_FATAL_FAILURE(assertNotCalled<NotifyPointerCaptureChangedArgs>(
+            "notifyPointerCaptureChanged() should not be called."));
+}
+
 template <class NotifyArgsType>
 void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message) {
     std::unique_lock<std::mutex> lock(mLock);
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 0ffcaaa..0a1dc4b 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -55,6 +55,7 @@
     void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr);
 
     void assertNotifyCaptureWasCalled(NotifyPointerCaptureChangedArgs* outEventArgs = nullptr);
+    void assertNotifyCaptureWasNotCalled();
     void assertNotifySensorWasCalled(NotifySensorArgs* outEventArgs = nullptr);
     void assertNotifyVibratorStateWasCalled(NotifyVibratorStateArgs* outEventArgs = nullptr);