Prevent events from getting backlogged.
This change implements two heuristics.
1. When events are older than 10 seconds, they are dropped.
2. If the application is currently busy processing an event and
the user touches a window belonging to a different application
then we drop the currently queued events so the other application
can start processing the gesture immediately.
Note that the system takes care of synthesizing cancelation events
automatically for any events that it drops.
Added some new handle types to allow the native dispatcher to
indirectly refer to the WindowManager's window state and app window
token. This was done to enable the dispatcher to identify the
application to which each window belongs but it also eliminates
some lookup tables and linear searches through the window list
on each key press.
Bug: 3224911
Change-Id: I9dae8dfe23d195d76865f97011fe2f1d351e2940
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 89a7751..b5a4bd2 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -72,6 +72,10 @@
// when an application takes too long to respond and the user has pressed an app switch key.
const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+// Amount of time to allow for an event to be dispatched (measured since its eventTime)
+// before considering it stale and dropping it.
+const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
+
static inline nsecs_t now() {
return systemTime(SYSTEM_TIME_MONOTONIC);
@@ -151,34 +155,12 @@
}
-// --- InputWindow ---
-
-bool InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
- return x >= touchableAreaLeft && x <= touchableAreaRight
- && y >= touchableAreaTop && y <= touchableAreaBottom;
-}
-
-bool InputWindow::frameContainsPoint(int32_t x, int32_t y) const {
- return x >= frameLeft && x <= frameRight
- && y >= frameTop && y <= frameBottom;
-}
-
-bool InputWindow::isTrustedOverlay() const {
- return layoutParamsType == TYPE_INPUT_METHOD
- || layoutParamsType == TYPE_INPUT_METHOD_DIALOG
- || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
-}
-
-bool InputWindow::supportsSplitTouch() const {
- return layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH;
-}
-
-
// --- InputDispatcher ---
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
mPolicy(policy),
- mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX),
+ mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
+ mNextUnblockedEvent(NULL),
mDispatchEnabled(true), mDispatchFrozen(false),
mFocusedWindow(NULL),
mFocusedApplication(NULL),
@@ -368,6 +350,7 @@
}
// Now we have an event to dispatch.
+ // All events are eventually dequeued and processed this way, even if we intend to drop them.
assert(mPendingEvent != NULL);
bool done = false;
DropReason dropReason = DROP_REASON_NOT_DROPPED;
@@ -376,6 +359,11 @@
} else if (!mDispatchEnabled) {
dropReason = DROP_REASON_DISABLED;
}
+
+ if (mNextUnblockedEvent == mPendingEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+
switch (mPendingEvent->type) {
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
ConfigurationChangedEntry* typedEntry =
@@ -395,6 +383,13 @@
dropReason = DROP_REASON_APP_SWITCH;
}
}
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout,
&dropReason, nextWakeupTime);
break;
@@ -405,6 +400,13 @@
if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
dropReason = DROP_REASON_APP_SWITCH;
}
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break;
@@ -431,6 +433,9 @@
switch (entry->type) {
case EventEntry::TYPE_KEY: {
+ // Optimize app switch latency.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the app switch key.
KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
if (isAppSwitchKeyEventLocked(keyEntry)) {
if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
@@ -448,11 +453,63 @@
}
break;
}
+
+ case EventEntry::TYPE_MOTION: {
+ // Optimize case where the current application is unresponsive and the user
+ // decides to touch a window in a different application.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the touch into the other window.
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
+ && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
+ && mInputTargetWaitApplication != NULL) {
+ int32_t x = int32_t(motionEntry->firstSample.pointerCoords[0].x);
+ int32_t y = int32_t(motionEntry->firstSample.pointerCoords[0].y);
+ const InputWindow* touchedWindow = findTouchedWindowAtLocked(x, y);
+ if (touchedWindow
+ && touchedWindow->inputWindowHandle != NULL
+ && touchedWindow->inputWindowHandle->getInputApplicationHandle()
+ != mInputTargetWaitApplication) {
+ // User touched a different application than the one we are waiting on.
+ // Flag the event, and start pruning the input queue.
+ mNextUnblockedEvent = motionEntry;
+ needWake = true;
+ }
+ }
+ break;
+ }
}
return needWake;
}
+const InputWindow* InputDispatcher::findTouchedWindowAtLocked(int32_t x, int32_t y) {
+ // Traverse windows from front to back to find touched window.
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const InputWindow* window = & mWindows.editItemAt(i);
+ int32_t flags = window->layoutParamsFlags;
+
+ if (window->visible) {
+ if (!(flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
+ bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
+ | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
+ // Found window.
+ return window;
+ }
+ }
+ }
+
+ if (flags & InputWindow::FLAG_SYSTEM_ERROR) {
+ // Error window is on top but not visible, so touch is dropped.
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) {
const char* reason;
switch (dropReason) {
@@ -470,6 +527,16 @@
LOGI("Dropped event because of pending overdue app switch.");
reason = "inbound event was dropped because of pending overdue app switch";
break;
+ case DROP_REASON_BLOCKED:
+ LOGI("Dropped event because the current application is not responding and the user "
+ "has started interating with a different application.");
+ reason = "inbound event was dropped because the current application is not responding "
+ "and the user has started interating with a different application";
+ break;
+ case DROP_REASON_STALE:
+ LOGI("Dropped event because it is stale.");
+ reason = "inbound event was dropped because it is stale";
+ break;
default:
assert(false);
return;
@@ -521,6 +588,10 @@
#endif
}
+bool InputDispatcher::isStaleEventLocked(nsecs_t currentTime, EventEntry* entry) {
+ return currentTime - entry->eventTime >= STALE_EVENT_TIMEOUT;
+}
+
bool InputDispatcher::runCommandsLockedInterruptible() {
if (mCommandQueue.isEmpty()) {
return false;
@@ -670,7 +741,7 @@
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
if (mFocusedWindow) {
- commandEntry->inputChannel = mFocusedWindow->inputChannel;
+ commandEntry->inputWindowHandle = mFocusedWindow->inputWindowHandle;
}
commandEntry->keyEntry = entry;
entry->refCount += 1;
@@ -848,6 +919,7 @@
mCurrentInputTargetsValid = false;
mCurrentInputTargets.clear();
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+ mInputTargetWaitApplication.clear();
}
void InputDispatcher::commitTargetsLocked() {
@@ -866,6 +938,7 @@
mInputTargetWaitStartTime = currentTime;
mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplication.clear();
}
} else {
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
@@ -880,6 +953,15 @@
mInputTargetWaitStartTime = currentTime;
mInputTargetWaitTimeoutTime = currentTime + timeout;
mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplication.clear();
+
+ if (window && window->inputWindowHandle != NULL) {
+ mInputTargetWaitApplication =
+ window->inputWindowHandle->getInputApplicationHandle();
+ }
+ if (mInputTargetWaitApplication == NULL && application) {
+ mInputTargetWaitApplication = application->inputApplicationHandle;
+ }
}
}
@@ -2624,7 +2706,7 @@
void InputDispatcher::releaseFocusedApplicationLocked() {
if (mFocusedApplication) {
mFocusedApplication = NULL;
- mFocusedApplicationStorage.handle.clear();
+ mFocusedApplicationStorage.inputApplicationHandle.clear();
}
}
@@ -2860,7 +2942,8 @@
}
}
-status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
#if DEBUG_REGISTRATION
LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
toString(monitor));
@@ -2875,7 +2958,7 @@
return BAD_VALUE;
}
- sp<Connection> connection = new Connection(inputChannel);
+ sp<Connection> connection = new Connection(inputChannel, inputWindowHandle);
status_t status = connection->initialize();
if (status) {
LOGE("Failed to initialize input publisher for input channel '%s', status=%d",
@@ -3002,9 +3085,10 @@
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doNotifyANRLockedInterruptible);
if (application) {
- commandEntry->inputApplicationHandle = application->handle;
+ commandEntry->inputApplicationHandle = application->inputApplicationHandle;
}
if (window) {
+ commandEntry->inputWindowHandle = window->inputWindowHandle;
commandEntry->inputChannel = window->inputChannel;
}
}
@@ -3025,7 +3109,7 @@
if (connection->status != Connection::STATUS_ZOMBIE) {
mLock.unlock();
- mPolicy->notifyInputChannelBroken(connection->inputChannel);
+ mPolicy->notifyInputChannelBroken(connection->inputWindowHandle);
mLock.lock();
}
@@ -3036,7 +3120,7 @@
mLock.unlock();
nsecs_t newTimeout = mPolicy->notifyANR(
- commandEntry->inputApplicationHandle, commandEntry->inputChannel);
+ commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle);
mLock.lock();
@@ -3052,7 +3136,7 @@
mLock.unlock();
- bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputChannel,
+ bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
&event, entry->policyFlags);
mLock.lock();
@@ -3095,7 +3179,7 @@
mLock.unlock();
- bool fallback = mPolicy->dispatchUnhandledKey(connection->inputChannel,
+ bool fallback = mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
&event, keyEntry->policyFlags, &event);
mLock.lock();
@@ -3604,8 +3688,10 @@
// --- InputDispatcher::Connection ---
-InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
- status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel),
+InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle) :
+ status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
+ inputPublisher(inputChannel),
lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) {
}