Merge "SyncPointerCapture (5/n): Move Pointer Capture to InputDispatcher"
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 9fea298..7c572b4 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -96,6 +96,8 @@
void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
+ void setPointerCapture(bool enabled) override {}
+
InputDispatcherConfiguration mConfig;
};
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 29df00b..d8a6548 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -114,6 +114,22 @@
return StringPrintf("FocusEvent(hasFocus=%s)", hasFocus ? "true" : "false");
}
+// --- PointerCaptureChangedEntry ---
+
+// 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)
+ : EventEntry(id, Type::POINTER_CAPTURE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
+ pointerCaptureEnabled(hasPointerCapture) {}
+
+PointerCaptureChangedEntry::~PointerCaptureChangedEntry() {}
+
+std::string PointerCaptureChangedEntry::getDescription() const {
+ return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
+ pointerCaptureEnabled ? "true" : "false");
+}
+
// --- KeyEntry ---
KeyEntry::KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 0661709..fba5514 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -36,6 +36,7 @@
FOCUS,
KEY,
MOTION,
+ POINTER_CAPTURE_CHANGED,
};
static const char* typeToString(Type type) {
@@ -50,6 +51,8 @@
return "KEY";
case Type::MOTION:
return "MOTION";
+ case Type::POINTER_CAPTURE_CHANGED:
+ return "POINTER_CAPTURE_CHANGED";
}
}
@@ -115,6 +118,15 @@
virtual ~FocusEntry();
};
+struct PointerCaptureChangedEntry : EventEntry {
+ bool pointerCaptureEnabled;
+
+ PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, bool hasPointerCapture);
+ std::string getDescription() const override;
+
+ virtual ~PointerCaptureChangedEntry();
+};
+
struct KeyEntry : EventEntry {
int32_t deviceId;
uint32_t source;
@@ -254,6 +266,7 @@
sp<IBinder> oldToken;
sp<IBinder> newToken;
std::string obscuringPackage;
+ bool enabled;
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index ed4f05a..6c44a54 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -454,6 +454,8 @@
mInTouchMode(true),
mMaximumObscuringOpacityForTouch(1.0f),
mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
+ mFocusedWindowRequestedPointerCapture(false),
+ mWindowTokenWithPointerCapture(nullptr),
mCompatService(getCompatService()) {
mLooper = new Looper(false);
mReporter = createInputReporter();
@@ -713,6 +715,14 @@
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ const auto typedEntry =
+ std::static_pointer_cast<PointerCaptureChangedEntry>(mPendingEvent);
+ dispatchPointerCaptureChangedLocked(currentTime, typedEntry, dropReason);
+ done = true;
+ break;
+ }
+
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
if (isAppSwitchDue) {
@@ -862,7 +872,8 @@
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
// nothing to do
break;
}
@@ -968,6 +979,10 @@
ALOGI("Dropped event because it is stale.");
reason = "inbound event was dropped because it is stale";
break;
+ case DropReason::NO_POINTER_CAPTURE:
+ ALOGI("Dropped event because there is no window with Pointer Capture.");
+ reason = "inbound event was dropped because there is no window with Pointer Capture";
+ break;
case DropReason::NOT_DROPPED: {
LOG_ALWAYS_FATAL("Should not be dropping a NOT_DROPPED event");
return;
@@ -991,6 +1006,9 @@
}
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ break;
+ }
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
@@ -1176,6 +1194,55 @@
dispatchEventLocked(currentTime, entry, {target});
}
+void InputDispatcher::dispatchPointerCaptureChangedLocked(
+ nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ DropReason& dropReason) {
+ const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr;
+ if (entry->pointerCaptureEnabled == haveWindowWithPointerCapture) {
+ LOG_ALWAYS_FATAL_IF(mFocusedWindowRequestedPointerCapture,
+ "The Pointer Capture state has already been dispatched to the window.");
+ // 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) {
+ // This can happen if a window requests capture and immediately releases capture.
+ ALOGW("No window requested Pointer Capture.");
+ return;
+ }
+ token = getValueByKey(mFocusedWindowTokenByDisplay, mFocusedDisplayId);
+ LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture.");
+ mWindowTokenWithPointerCapture = token;
+ } else {
+ // Disable Pointer Capture
+ token = mWindowTokenWithPointerCapture;
+ mWindowTokenWithPointerCapture = nullptr;
+ mFocusedWindowRequestedPointerCapture = false;
+ }
+
+ auto channel = getInputChannelLocked(token);
+ if (channel == nullptr) {
+ // Window has gone away, clean up Pointer Capture state.
+ mWindowTokenWithPointerCapture = nullptr;
+ mFocusedWindowRequestedPointerCapture = false;
+ return;
+ }
+ InputTarget target;
+ target.inputChannel = channel;
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ entry->dispatchInProgress = true;
+ dispatchEventLocked(currentTime, entry, {target});
+
+ dropReason = DropReason::NOT_DROPPED;
+}
+
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Preprocessing.
@@ -1483,6 +1550,7 @@
displayId = motionEntry.displayId;
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED:
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
@@ -2366,8 +2434,10 @@
}
void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
- if (eventEntry.type == EventEntry::Type::FOCUS) {
- // Focus events are passed to apps, but do not represent user activity.
+ if (eventEntry.type == EventEntry::Type::FOCUS ||
+ eventEntry.type == EventEntry::Type::POINTER_CAPTURE_CHANGED) {
+ // Focus or pointer capture changed events are passed to apps, but do not represent user
+ // activity.
return;
}
int32_t displayId = getTargetDisplayId(eventEntry);
@@ -2405,7 +2475,8 @@
}
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
LOG_ALWAYS_FATAL("%s events are not user activity",
EventEntry::typeToString(eventEntry.type));
break;
@@ -2617,7 +2688,8 @@
break;
}
- case EventEntry::Type::FOCUS: {
+ case EventEntry::Type::FOCUS:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
@@ -2821,6 +2893,7 @@
reportTouchEventForStatistics(motionEntry);
break;
}
+
case EventEntry::Type::FOCUS: {
const FocusEntry& focusEntry = static_cast<const FocusEntry&>(eventEntry);
status = connection->inputPublisher.publishFocusEvent(dispatchEntry->seq,
@@ -2830,6 +2903,15 @@
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ const auto& captureEntry =
+ static_cast<const PointerCaptureChangedEntry&>(eventEntry);
+ status = connection->inputPublisher
+ .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
+ captureEntry.pointerCaptureEnabled);
+ break;
+ }
+
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
LOG_ALWAYS_FATAL("Should never start dispatch cycles for %s events",
@@ -3124,8 +3206,10 @@
static_cast<const MotionEntry&>(*cancelationEventEntry));
break;
}
- case EventEntry::Type::FOCUS: {
- LOG_ALWAYS_FATAL("Canceling focus events is not supported");
+ case EventEntry::Type::FOCUS:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ LOG_ALWAYS_FATAL("Canceling %s events is not supported",
+ EventEntry::typeToString(cancelationEventEntry->type));
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
@@ -3185,7 +3269,8 @@
case EventEntry::Type::KEY:
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
EventEntry::typeToString(downEventEntry->type));
break;
@@ -3562,7 +3647,17 @@
args->enabled ? "true" : "false");
#endif
- // TODO(prabirmsp): Implement.
+ bool needWake;
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ auto entry = std::make_unique<PointerCaptureChangedEntry>(args->id, args->eventTime,
+ args->enabled);
+ needWake = enqueueInboundEventLocked(std::move(entry));
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
}
InputEventInjectionResult InputDispatcher::injectInputEvent(
@@ -4456,6 +4551,24 @@
return dump;
}
+std::string InputDispatcher::dumpPointerCaptureStateLocked() {
+ std::string dump;
+
+ dump += StringPrintf(INDENT "FocusedWindowRequestedPointerCapture: %s\n",
+ toString(mFocusedWindowRequestedPointerCapture));
+
+ std::string windowName = "None";
+ if (mWindowTokenWithPointerCapture) {
+ const sp<InputWindowHandle> captureWindowHandle =
+ getWindowHandleLocked(mWindowTokenWithPointerCapture);
+ windowName = captureWindowHandle ? captureWindowHandle->getName().c_str()
+ : "token has capture without window";
+ }
+ dump += StringPrintf(INDENT "CurrentWindowWithPointerCapture: %s\n", windowName.c_str());
+
+ return dump;
+}
+
void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
@@ -4479,6 +4592,7 @@
dump += dumpFocusedWindowsLocked();
dump += dumpPendingFocusRequestsLocked();
+ dump += dumpPointerCaptureStateLocked();
if (!mTouchStatesByDisplay.empty()) {
dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
@@ -4860,6 +4974,39 @@
return OK;
}
+void InputDispatcher::requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ if (DEBUG_FOCUS) {
+ const sp<InputWindowHandle> windowHandle = getWindowHandleLocked(windowToken);
+ ALOGI("Request to %s Pointer Capture from: %s.", enabled ? "enable" : "disable",
+ windowHandle != nullptr ? windowHandle->getName().c_str()
+ : "token without window");
+ }
+
+ const sp<IBinder> focusedToken =
+ getValueByKey(mFocusedWindowTokenByDisplay, mFocusedDisplayId);
+ if (focusedToken != windowToken) {
+ ALOGW("Ignoring request to %s Pointer Capture: window does not have focus.",
+ enabled ? "enable" : "disable");
+ return;
+ }
+
+ if (enabled == mFocusedWindowRequestedPointerCapture) {
+ 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
+
+ // Wake the thread to process command entries.
+ mLooper->wake();
+}
+
std::optional<int32_t> InputDispatcher::findGestureMonitorDisplayByTokenLocked(
const sp<IBinder>& token) {
for (const auto& it : mGestureMonitorsByDisplay) {
@@ -5578,11 +5725,50 @@
enqueueFocusEventLocked(newFocusedToken, true /*hasFocus*/, reason);
}
+ // If a window has pointer capture, then it must have focus. We need to ensure that this
+ // contract is upheld when pointer capture is being disabled due to a loss of window focus.
+ // If the window loses focus before it loses pointer capture, then the window can be in a state
+ // where it has pointer capture but not focus, violating the contract. Therefore we must
+ // dispatch the pointer capture event before the focus event. Since focus events are added to
+ // the front of the queue (above), we add the pointer capture event to the front of the queue
+ // after the focus events are added. This ensures the pointer capture event ends up at the
+ // front.
+ disablePointerCaptureForcedLocked();
+
if (mFocusedDisplayId == displayId) {
notifyFocusChangedLocked(oldFocusedToken, newFocusedToken);
}
}
+void InputDispatcher::disablePointerCaptureForcedLocked() {
+ if (!mFocusedWindowRequestedPointerCapture && !mWindowTokenWithPointerCapture) {
+ return;
+ }
+
+ ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
+
+ if (mFocusedWindowRequestedPointerCapture) {
+ mFocusedWindowRequestedPointerCapture = false;
+ setPointerCaptureLocked(false);
+ }
+
+ if (!mWindowTokenWithPointerCapture) {
+ // No need to send capture changes because no window has capture.
+ return;
+ }
+
+ if (mPendingEvent != nullptr) {
+ // Move the pending event to the front of the queue. This will give the chance
+ // for the pending event to be dropped if it is a captured event.
+ mInboundQueue.push_front(mPendingEvent);
+ mPendingEvent = nullptr;
+ }
+
+ auto entry = std::make_unique<PointerCaptureChangedEntry>(mIdGenerator.nextId(), now(),
+ false /* hasCapture */);
+ mInboundQueue.push_front(std::move(entry));
+}
+
/**
* Checks if the window token can be focused on a display. The token can be focused if there is
* at least one window handle that is visible with the same token and all window handles with the
@@ -5626,4 +5812,21 @@
return FocusResult::OK;
}
+
+void InputDispatcher::setPointerCaptureLocked(bool enabled) {
+ std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
+ &InputDispatcher::doSetPointerCaptureLockedInterruptible);
+ commandEntry->enabled = enabled;
+ postCommandLocked(std::move(commandEntry));
+}
+
+void InputDispatcher::doSetPointerCaptureLockedInterruptible(
+ android::inputdispatcher::CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->setPointerCapture(commandEntry->enabled);
+
+ mLock.lock();
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 8f58785..5d37645 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -127,6 +127,7 @@
int32_t displayId, bool isGestureMonitor, const std::string& name) override;
virtual status_t removeInputChannel(const sp<IBinder>& connectionToken) override;
virtual status_t pilferPointers(const sp<IBinder>& token) override;
+ virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) override;
std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
@@ -138,6 +139,7 @@
DISABLED,
BLOCKED,
STALE,
+ NO_POINTER_CAPTURE,
};
enum class FocusResult {
@@ -351,6 +353,21 @@
// Top focused display.
int32_t mFocusedDisplayId 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 window token that has Pointer Capture.
+ // This should be in sync with PointerCaptureChangedEvents dispatched to the input channel.
+ sp<IBinder> mWindowTokenWithPointerCapture GUARDED_BY(mLock);
+
+ // Disable Pointer Capture as a result of loss of window focus.
+ void disablePointerCaptureForcedLocked() REQUIRES(mLock);
+
+ // Set the Pointer Capture state in the Policy.
+ void setPointerCaptureLocked(bool enabled) REQUIRES(mLock);
+
// Dispatcher state at time of last ANR.
std::string mLastAnrState GUARDED_BY(mLock);
@@ -370,6 +387,9 @@
DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry)
REQUIRES(mLock);
+ void dispatchPointerCaptureChangedLocked(
+ nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ DropReason& dropReason) REQUIRES(mLock);
void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<EventEntry> entry,
const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
@@ -533,6 +553,7 @@
void logDispatchStateLocked() REQUIRES(mLock);
std::string dumpFocusedWindowsLocked() REQUIRES(mLock);
std::string dumpPendingFocusRequestsLocked() REQUIRES(mLock);
+ std::string dumpPointerCaptureStateLocked() REQUIRES(mLock);
// Registration.
void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
@@ -575,6 +596,7 @@
void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry)
REQUIRES(mLock);
void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ void doSetPointerCaptureLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
bool afterKeyEventLockedInterruptible(const sp<Connection>& connection,
DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
bool handled) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 9154d48..2909d69 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -186,6 +186,13 @@
* This method may be called on any thread (usually by the input manager).
*/
virtual status_t pilferPointers(const sp<IBinder>& token) = 0;
+
+ /**
+ * Enables Pointer Capture on the specified window if the window has focus.
+ *
+ * InputDispatcher is the source of truth of Pointer Capture.
+ */
+ virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) = 0;
};
} // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 1125257..c1821ab 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -134,6 +134,12 @@
* The touchedToken passed as an argument is the window that received the input event.
*/
virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) = 0;
+
+ /* Change the Pointer Capture state in InputReader.
+ *
+ * InputDispatcher is solely responsible for updating the Pointer Capture state.
+ */
+ virtual void setPointerCapture(bool enabled) = 0;
};
} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 5ab2ae3..7632814 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -211,6 +211,33 @@
mConfig.keyRepeatDelay = delay;
}
+ void waitForSetPointerCapture(bool enabled) {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+
+ if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms,
+ [this, enabled]() REQUIRES(mLock) {
+ return mPointerCaptureEnabled &&
+ *mPointerCaptureEnabled ==
+ enabled;
+ })) {
+ FAIL() << "Timed out waiting for setPointerCapture(" << enabled << ") to be called.";
+ }
+ mPointerCaptureEnabled.reset();
+ }
+
+ void assertSetPointerCaptureNotCalled() {
+ std::unique_lock lock(mLock);
+ 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. "
+ "enabled = "
+ << *mPointerCaptureEnabled;
+ }
+ mPointerCaptureEnabled.reset();
+ }
+
private:
std::mutex mLock;
std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -218,6 +245,9 @@
sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
+ std::condition_variable mPointerCaptureChangedCondition;
+ std::optional<bool> mPointerCaptureEnabled GUARDED_BY(mLock);
+
// ANR handling
std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
std::queue<sp<IBinder>> mAnrConnectionTokens GUARDED_BY(mLock);
@@ -307,6 +337,12 @@
mOnPointerDownToken = newToken;
}
+ void setPointerCapture(bool enabled) override {
+ std::scoped_lock lock(mLock);
+ mPointerCaptureEnabled = {enabled};
+ mPointerCaptureChangedCondition.notify_all();
+ }
+
void assertFilterInputEventWasCalled(int type, nsecs_t eventTime, int32_t action,
int32_t displayId) {
std::scoped_lock lock(mLock);
@@ -674,6 +710,9 @@
case AINPUT_EVENT_TYPE_FOCUS: {
FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
}
+ case AINPUT_EVENT_TYPE_CAPTURE: {
+ FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
+ }
default: {
FAIL() << mName.c_str() << ": invalid event type: " << expectedEventType;
}
@@ -696,6 +735,21 @@
EXPECT_EQ(inTouchMode, focusEvent->getInTouchMode());
}
+ void consumeCaptureEvent(bool hasCapture) {
+ const InputEvent* event = consume();
+ ASSERT_NE(nullptr, event) << mName.c_str()
+ << ": consumer should have returned non-NULL event.";
+ ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType())
+ << "Got " << inputEventTypeToString(event->getType())
+ << " event instead of CAPTURE event";
+
+ ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
+ << mName.c_str() << ": event displayId should always be NONE.";
+
+ const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
+ EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
+ }
+
void assertNoEvents() {
InputEvent* event = consume();
if (event == nullptr) {
@@ -713,6 +767,10 @@
FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
ADD_FAILURE() << "Received focus event, hasFocus = "
<< (focusEvent.getHasFocus() ? "true" : "false");
+ } else if (event->getType() == AINPUT_EVENT_TYPE_CAPTURE) {
+ const auto& captureEvent = static_cast<CaptureEvent&>(*event);
+ ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
+ << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
}
FAIL() << mName.c_str()
<< ": should not have received any events, so consume() should return NULL";
@@ -859,6 +917,12 @@
mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode);
}
+ void consumeCaptureEvent(bool hasCapture) {
+ ASSERT_NE(mInputReceiver, nullptr)
+ << "Cannot consume events from a window with no receiver";
+ mInputReceiver->consumeCaptureEvent(hasCapture);
+ }
+
void consumeEvent(int32_t expectedEventType, int32_t expectedAction, int32_t expectedDisplayId,
int32_t expectedFlags) {
ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
@@ -1138,6 +1202,10 @@
return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
}
+static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(bool enabled) {
+ return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), enabled);
+}
+
TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window = new FakeWindowHandle(application, mDispatcher, "Fake Window",
@@ -3824,4 +3892,88 @@
// window gets the pending key event
mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
}
+
+class InputDispatcherPointerCaptureTests : public InputDispatcherTest {
+protected:
+ std::shared_ptr<FakeApplicationHandle> mApp;
+ sp<FakeWindowHandle> mWindow;
+ sp<FakeWindowHandle> mSecondWindow;
+
+ void SetUp() override {
+ InputDispatcherTest::SetUp();
+ mApp = std::make_shared<FakeApplicationHandle>();
+ mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mWindow->setFocusable(true);
+ mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+ mSecondWindow->setFocusable(true);
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
+
+ setFocusedWindow(mWindow);
+ mWindow->consumeFocusEvent(true);
+ }
+
+ void notifyPointerCaptureChanged(bool enabled) {
+ const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(enabled);
+ mDispatcher->notifyPointerCaptureChanged(&args);
+ }
+
+ void requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window, bool enabled) {
+ mDispatcher->requestPointerCapture(window->getToken(), enabled);
+ mFakePolicy->waitForSetPointerCapture(enabled);
+ notifyPointerCaptureChanged(enabled);
+ window->consumeCaptureEvent(enabled);
+ }
+};
+
+TEST_F(InputDispatcherPointerCaptureTests, EnablePointerCaptureWhenFocused) {
+ // Ensure that capture cannot be obtained for unfocused windows.
+ mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+ mSecondWindow->assertNoEvents();
+
+ // Ensure that capture can be enabled from the focus window.
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ // Ensure that capture cannot be disabled from a window that does not have capture.
+ mDispatcher->requestPointerCapture(mSecondWindow->getToken(), false);
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+
+ // Ensure that capture can be disabled from the window with capture.
+ requestAndVerifyPointerCapture(mWindow, false);
+}
+
+TEST_F(InputDispatcherPointerCaptureTests, DisablesPointerCaptureAfterWindowLosesFocus) {
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ setFocusedWindow(mSecondWindow);
+
+ // Ensure that the capture disabled event was sent first.
+ mWindow->consumeCaptureEvent(false);
+ mWindow->consumeFocusEvent(false);
+ mSecondWindow->consumeFocusEvent(true);
+ mFakePolicy->waitForSetPointerCapture(false);
+
+ // Ensure that additional state changes from InputReader are not sent to the window.
+ notifyPointerCaptureChanged(false);
+ notifyPointerCaptureChanged(true);
+ notifyPointerCaptureChanged(false);
+ mWindow->assertNoEvents();
+ mSecondWindow->assertNoEvents();
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+}
+
+TEST_F(InputDispatcherPointerCaptureTests, UnexpectedStateChangeDisablesPointerCapture) {
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ // InputReader unexpectedly disables and enables pointer capture.
+ notifyPointerCaptureChanged(false);
+ notifyPointerCaptureChanged(true);
+
+ // Ensure that Pointer Capture is disabled.
+ mWindow->consumeCaptureEvent(false);
+ mWindow->assertNoEvents();
+}
+
} // namespace android::inputdispatcher