Add unit tests for native input and fix bugs identified.

Fixed a bug where we would lose the first touch point when swiping out of
the virtual key area.

Fixed a bug where we would not send an ACTION_MOVE event in cases where
individual pointers went down/up and the remaining pointers actually moved.
This is important since many applications do not handle pointer movements
during ACTION_POINTER_DOWN or ACTION_POINTER_UP.  In the case of
ACTION_POINTER_UP the movement was completely lost since all pointers were
dispatched using their old location rather than the new location.

Improved motion event validation to check for duplicate pointer ids.

Added an input source constant that was missing from the NDK api but
defined in the framework api.

Added a timestamp when reporting added/removed devices in EventHub.

Bug: 3070082
Change-Id: I3206a030f43b7616e2f48006e5a9d522c4d92e56
This commit is contained in:
Jeff Brown 2010-10-20 15:33:38 -07:00
parent 48a862ef5f
commit 3c3cc62e24
7 changed files with 3670 additions and 26 deletions

View File

@ -170,11 +170,10 @@ public:
* and parameters maintained by the input reader.
*/
class InputReaderContext {
protected:
public:
InputReaderContext() { }
virtual ~InputReaderContext() { }
public:
virtual void updateGlobalMetaState() = 0;
virtual int32_t getGlobalMetaState() = 0;
@ -193,7 +192,7 @@ public:
* the input reader, the input reader never calls into other components while holding
* an exclusive internal lock whenever re-entrance can happen.
*/
class InputReader : public InputReaderInterface, private InputReaderContext {
class InputReader : public InputReaderInterface, protected InputReaderContext {
public:
InputReader(const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& policy,
@ -219,6 +218,11 @@ public:
virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
protected:
// These methods are protected virtual so they can be overridden and instrumented
// by test cases.
virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
private:
sp<EventHubInterface> mEventHub;
sp<InputReaderPolicyInterface> mPolicy;
@ -244,12 +248,11 @@ private:
void addDevice(int32_t deviceId);
void removeDevice(int32_t deviceId);
InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
void configureExcludedDevices();
void consumeEvent(const RawEvent* rawEvent);
void handleConfigurationChanged();
void handleConfigurationChanged(nsecs_t when);
// state management for all devices
Mutex mStateLock;
@ -533,6 +536,21 @@ protected:
int32_t toolMajor;
int32_t toolMinor;
int32_t orientation;
inline bool operator== (const PointerData& other) const {
return id == other.id
&& x == other.x
&& y == other.y
&& pressure == other.pressure
&& touchMajor == other.touchMajor
&& touchMinor == other.touchMinor
&& toolMajor == other.toolMajor
&& toolMinor == other.toolMinor
&& orientation == other.orientation;
}
inline bool operator!= (const PointerData& other) const {
return !(*this == other);
}
};
// Raw data for a collection of pointers including a pointer id mapping table.

View File

@ -370,6 +370,7 @@ bool EventHub::getEvent(RawEvent* outEvent)
outEvent->deviceId = device->id;
}
outEvent->type = DEVICE_REMOVED;
outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
delete device;
mNeedToSendFinishedDeviceScan = true;
return true;
@ -386,6 +387,7 @@ bool EventHub::getEvent(RawEvent* outEvent)
outEvent->deviceId = device->id;
}
outEvent->type = DEVICE_ADDED;
outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
mNeedToSendFinishedDeviceScan = true;
return true;
}
@ -393,6 +395,7 @@ bool EventHub::getEvent(RawEvent* outEvent)
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
outEvent->type = FINISHED_DEVICE_SCAN;
outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
return true;
}

View File

@ -124,12 +124,19 @@ static bool validateMotionEvent(int32_t action, size_t pointerCount,
pointerCount, MAX_POINTERS);
return false;
}
BitSet32 pointerIdBits;
for (size_t i = 0; i < pointerCount; i++) {
if (pointerIds[i] < 0 || pointerIds[i] > MAX_POINTER_ID) {
int32_t id = pointerIds[i];
if (id < 0 || id > MAX_POINTER_ID) {
LOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
pointerIds[i], MAX_POINTER_ID);
id, MAX_POINTER_ID);
return false;
}
if (pointerIdBits.hasBit(id)) {
LOGE("Motion event has duplicate pointer id %d", id);
return false;
}
pointerIdBits.markBit(id);
}
return true;
}

View File

@ -234,7 +234,7 @@ void InputReader::process(const RawEvent* rawEvent) {
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChanged();
handleConfigurationChanged(rawEvent->when);
break;
default:
@ -372,7 +372,7 @@ void InputReader::consumeEvent(const RawEvent* rawEvent) {
} // release device registry reader lock
}
void InputReader::handleConfigurationChanged() {
void InputReader::handleConfigurationChanged(nsecs_t when) {
// Reset global meta state because it depends on the list of all configured devices.
updateGlobalMetaState();
@ -380,7 +380,6 @@ void InputReader::handleConfigurationChanged() {
updateInputConfiguration();
// Enqueue configuration changed.
nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
mDispatcher->notifyConfigurationChanged(when);
}
@ -2094,7 +2093,7 @@ TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
mLocked.currentVirtualKey.down = false;
#if DEBUG_VIRTUAL_KEYS
LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
#endif
keyEventAction = AKEY_EVENT_ACTION_UP;
keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
@ -2119,13 +2118,22 @@ TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
mLocked.currentVirtualKey.down = false;
#if DEBUG_VIRTUAL_KEYS
LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
#endif
keyEventAction = AKEY_EVENT_ACTION_UP;
keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
| AKEY_EVENT_FLAG_CANCELED;
touchResult = DROP_STROKE;
goto DispatchVirtualKey;
// Check whether the pointer moved inside the display area where we should
// start a new stroke.
int32_t x = mCurrentTouch.pointers[0].x;
int32_t y = mCurrentTouch.pointers[0].y;
if (isPointInsideSurfaceLocked(x, y)) {
mLastTouch.clear();
touchResult = DISPATCH_TOUCH;
} else {
touchResult = DROP_STROKE;
}
} else {
if (mCurrentTouch.pointerCount >= 1 && mLastTouch.pointerCount == 0) {
// Pointer just went down. Handle off-screen touches, if needed.
@ -2143,7 +2151,8 @@ TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
mLocked.currentVirtualKey.scanCode = virtualKey->scanCode;
#if DEBUG_VIRTUAL_KEYS
LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
mLocked.currentVirtualKey.keyCode,
mLocked.currentVirtualKey.scanCode);
#endif
keyEventAction = AKEY_EVENT_ACTION_DOWN;
keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM
@ -2190,14 +2199,35 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
dispatchTouch(when, policyFlags, & mCurrentTouch,
currentIdBits, -1, currentPointerCount, motionEventAction);
} else {
// There may be pointers going up and pointers going down at the same time when pointer
// ids are reported by the device driver.
// There may be pointers going up and pointers going down and pointers moving
// all at the same time.
BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value);
BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value);
BitSet32 activeIdBits(lastIdBits.value);
uint32_t pointerCount = lastPointerCount;
while (! upIdBits.isEmpty()) {
// Produce an intermediate representation of the touch data that consists of the
// old location of pointers that have just gone up and the new location of pointers that
// have just moved but omits the location of pointers that have just gone down.
TouchData interimTouch;
interimTouch.copyFrom(mLastTouch);
BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
bool moveNeeded = false;
while (!moveIdBits.isEmpty()) {
uint32_t moveId = moveIdBits.firstMarkedBit();
moveIdBits.clearBit(moveId);
int32_t oldIndex = mLastTouch.idToIndex[moveId];
int32_t newIndex = mCurrentTouch.idToIndex[moveId];
if (mLastTouch.pointers[oldIndex] != mCurrentTouch.pointers[newIndex]) {
interimTouch.pointers[oldIndex] = mCurrentTouch.pointers[newIndex];
moveNeeded = true;
}
}
// Dispatch pointer up events using the interim pointer locations.
while (!upIdBits.isEmpty()) {
uint32_t upId = upIdBits.firstMarkedBit();
upIdBits.clearBit(upId);
BitSet32 oldActiveIdBits = activeIdBits;
@ -2210,12 +2240,21 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP;
}
dispatchTouch(when, policyFlags, & mLastTouch,
dispatchTouch(when, policyFlags, &interimTouch,
oldActiveIdBits, upId, pointerCount, motionEventAction);
pointerCount -= 1;
}
while (! downIdBits.isEmpty()) {
// Dispatch move events if any of the remaining pointers moved from their old locations.
// Although applications receive new locations as part of individual pointer up
// events, they do not generally handle them except when presented in a move event.
if (moveNeeded) {
dispatchTouch(when, policyFlags, &mCurrentTouch,
activeIdBits, -1, pointerCount, AMOTION_EVENT_ACTION_MOVE);
}
// Dispatch pointer down events using the new pointer locations.
while (!downIdBits.isEmpty()) {
uint32_t downId = downIdBits.firstMarkedBit();
downIdBits.clearBit(downId);
BitSet32 oldActiveIdBits = activeIdBits;
@ -2230,7 +2269,7 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
}
pointerCount += 1;
dispatchTouch(when, policyFlags, & mCurrentTouch,
dispatchTouch(when, policyFlags, &mCurrentTouch,
activeIdBits, downId, pointerCount, motionEventAction);
}
}
@ -3339,8 +3378,8 @@ void MultiTouchInputMapper::sync(nsecs_t when) {
if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
if (inPointer.absMTPressure <= 0) {
// Some devices send sync packets with X / Y but with a 0 presure to indicate
// a pointer up. Drop this finger.
// Some devices send sync packets with X / Y but with a 0 pressure to indicate
// a pointer going up. Drop this finger.
continue;
}
outPointer.pressure = inPointer.absMTPressure;

View File

@ -7,6 +7,7 @@ ifneq ($(TARGET_SIMULATOR),true)
# Build the unit tests.
test_src_files := \
InputChannel_test.cpp \
InputReader_test.cpp \
InputDispatcher_test.cpp \
InputPublisherAndConsumer_test.cpp

View File

@ -4,15 +4,223 @@
#include <ui/InputDispatcher.h>
#include <gtest/gtest.h>
#include <linux/input.h>
namespace android {
class InputDispatcherTest : public testing::Test {
// An arbitrary time value.
static const nsecs_t ARBITRARY_TIME = 1234;
// An arbitrary device id.
static const int32_t DEVICE_ID = 1;
// An arbitrary injector pid / uid pair that has permission to inject events.
static const int32_t INJECTOR_PID = 999;
static const int32_t INJECTOR_UID = 1001;
// --- FakeInputDispatcherPolicy ---
class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
protected:
virtual ~FakeInputDispatcherPolicy() {
}
public:
FakeInputDispatcherPolicy() {
}
private:
virtual void notifyConfigurationChanged(nsecs_t when) {
}
virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
const sp<InputChannel>& inputChannel) {
return 0;
}
virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) {
}
virtual nsecs_t getKeyRepeatTimeout() {
return 500 * 1000000LL;
}
virtual nsecs_t getKeyRepeatDelay() {
return 50 * 1000000LL;
}
virtual int32_t getMaxEventsPerSecond() {
return 60;
}
virtual void interceptKeyBeforeQueueing(nsecs_t when, int32_t deviceId,
int32_t action, int32_t& flags, int32_t keyCode, int32_t scanCode,
uint32_t& policyFlags) {
}
virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
}
virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
const KeyEvent* keyEvent, uint32_t policyFlags) {
return false;
}
virtual void notifySwitch(nsecs_t when,
int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
}
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
}
virtual bool checkInjectEventsPermissionNonReentrant(
int32_t injectorPid, int32_t injectorUid) {
return false;
}
};
TEST_F(InputDispatcherTest, Dummy) {
// TODO
// --- InputDispatcherTest ---
class InputDispatcherTest : public testing::Test {
protected:
sp<FakeInputDispatcherPolicy> mFakePolicy;
sp<InputDispatcher> mDispatcher;
virtual void SetUp() {
mFakePolicy = new FakeInputDispatcherPolicy();
mDispatcher = new InputDispatcher(mFakePolicy);
}
virtual void TearDown() {
mFakePolicy.clear();
mDispatcher.clear();
}
};
TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
KeyEvent event;
// Rejects undefined key actions.
event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
/*action*/ -1, 0,
AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject key events with undefined action.";
// Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
AKEY_EVENT_ACTION_MULTIPLE, 0,
AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject key events with ACTION_MULTIPLE.";
}
TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
MotionEvent event;
int32_t pointerIds[MAX_POINTERS + 1];
PointerCoords pointerCoords[MAX_POINTERS + 1];
for (int i = 0; i <= MAX_POINTERS; i++) {
pointerIds[i] = i;
}
// Rejects undefined motion actions.
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
/*action*/ -1, 0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with undefined action.";
// Rejects pointer down with invalid index.
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with pointer down index too large.";
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_POINTER_DOWN | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with pointer down index too small.";
// Rejects pointer up with invalid index.
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with pointer up index too large.";
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_POINTER_UP | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with pointer up index too small.";
// Rejects motion events with invalid number of pointers.
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 0, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with 0 pointers.";
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with more than MAX_POINTERS pointers.";
// Rejects motion events with invalid pointer ids.
pointerIds[0] = -1;
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with pointer ids less than 0.";
pointerIds[0] = MAX_POINTER_ID + 1;
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 1, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
// Rejects motion events with duplicate pointer ids.
pointerIds[0] = 1;
pointerIds[1] = 1;
event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
ARBITRARY_TIME, ARBITRARY_TIME,
/*pointerCount*/ 2, pointerIds, pointerCoords);
ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
<< "Should reject motion events with duplicate pointer ids.";
}
} // namespace android

File diff suppressed because it is too large Load Diff