InputDispatcher: check consistency of filtered injected events
One of the common problems that InputDispatcher has to deal with is the
consistency of the incoming event streams.
Having consistent event streams allows us to make assumptions about the
possible events that we may receive in each dispatching situation. This
allows us to write simpler code with less branching and clearer logic.
Historically, there hasn't been much event verification of incoming
streams except for basic event property checking. Recently added
InputVerifier helps with checking of the incoming event consistency.
In dispatcher, there are two ways that events can enter:
1) via "real" hardware devices - notifyMotion (and other notify* calls
like notifyKey, etc).
2) via injection
We have complete control over the notifyMotion behaviour, but
even that poses a problem today. The following stream: HOVER_MOVE ->
ACTION_DOWN is possible. Here, the correct stream would have been:
HOVER_ENTER -> HOVER_MOVE -> HOVER_EXIT -> ACTION_DOWN. Fixing this
would require non-trivial changes in several input mappers.
The situation with injection is more difficult. The injection is coming
from an external source - typically a test that may or may not pass.
Many tests inject inconsistent events, like ACTION_DOWN -> ACTION_DOWN.
However, even if a test is always injecting consistent events, we could
still have problems. One test could run and then not inject a complete
sequence (maybe it's a flaky test that just failed for a different
reason). This would leave some dangling pointers behind. Then another test
would run, and may try to inject events with the same device id (*).
This would conflict with the current dispatcher state.
(*) In practice, today we don't allow anyone except for a11y to specify
the device id. All injected events have device id = VIRTUAL_KEYBOARD_ID
except for those injected by a11y.
This can be partially fixed by using targeted injection, but some tests
do require interaction with the entire system, such as creating and
launching non-instrumented apps, or testing the behaviour of the actual
global gesture monitors.
Unlike tests, a11y is allowed to impersonate real hardware devices. So
the events injected from a11y will potentially intermix with the
dispatcher state stored developed from the 'notify' calls. Therefore,
a11y must be careful when selecting which streams to consume, and which
events to inject back into dispatcher. Consuming incomplete streams, or
injecting inconsistent events have the potential to crash the
dispatcher.
In this CL, we are checking the consistency of the events that are
injected from the InputFilter, and dumping the dispatcher state, and the
inconsistent event itself.
This way, we could use these logs to check if they potentially precede a
dispatcher crash. This would help point to the root cause.
In the future, we could consider failing the injection and throwing the
reason back to the injecting code, potentially in the form of an
exception.
Bug: 307690884
Test: TEST=libinput_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST
Change-Id: I9e85499d8470c2f6dc572a0299813b9e3bd4438a
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index ab23893..47b9a0c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -4551,6 +4551,7 @@
}
void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ // TODO(b/308677868) Remove device reset from the InputListener interface
if (debugInboundEventDetails()) {
ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime,
args.deviceId);
@@ -4692,6 +4693,30 @@
}
mLock.lock();
+
+ if (policyFlags & POLICY_FLAG_FILTERED) {
+ // The events from InputFilter impersonate real hardware devices. Check these
+ // events for consistency and print an error. An inconsistent event sent from
+ // InputFilter could cause a crash in the later stages of dispatching pipeline.
+ auto [it, _] =
+ mInputFilterVerifiersByDisplay
+ .try_emplace(displayId,
+ StringPrintf("Injection on %" PRId32, displayId));
+ InputVerifier& verifier = it->second;
+
+ Result<void> result =
+ verifier.processMovement(resolvedDeviceId, motionEvent.getSource(),
+ motionEvent.getAction(),
+ motionEvent.getPointerCount(),
+ motionEvent.getPointerProperties(),
+ motionEvent.getSamplePointerCoords(), flags);
+ if (!result.ok()) {
+ logDispatchStateLocked();
+ LOG(ERROR) << "Inconsistent event: " << motionEvent
+ << ", reason: " << result.error();
+ }
+ }
+
const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
const size_t pointerCount = motionEvent.getPointerCount();
const std::vector<PointerProperties>
@@ -6756,6 +6781,7 @@
// Remove the associated touch mode state.
mTouchModePerDisplay.erase(displayId);
mVerifiersByDisplay.erase(displayId);
+ mInputFilterVerifiersByDisplay.erase(displayId);
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.