diff options
author | Jeff Brown <jeffbrown@google.com> | 2010-06-16 01:53:36 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2010-06-17 13:27:16 -0700 |
commit | f4a4ec2063dfd28e04bbfe712f67acee4bdc8e37 (patch) | |
tree | 9d52c784ebd68acfc44a2cdb06d7cf057bba2c91 /libs/ui | |
parent | e85dafb4d625cce230695127c39636a40932b313 (diff) | |
download | frameworks_native-f4a4ec2063dfd28e04bbfe712f67acee4bdc8e37.zip frameworks_native-f4a4ec2063dfd28e04bbfe712f67acee4bdc8e37.tar.gz frameworks_native-f4a4ec2063dfd28e04bbfe712f67acee4bdc8e37.tar.bz2 |
Even more native input dispatch work in progress.
Added more tests.
Fixed a regression in Vector.
Fixed bugs in pointer tracking.
Fixed a starvation issue in PollLoop when setting or removing callbacks.
Fixed a couple of policy nits.
Modified the internal representation of MotionEvent to be more
efficient and more consistent.
Added code to skip/cancel virtual key processing when there are multiple
pointers down. This helps to better disambiguate virtual key presses
from stray touches (such as cheek presses).
Change-Id: I2a7d2cce0195afb9125b23378baa94fd2fc6671c
Diffstat (limited to 'libs/ui')
-rw-r--r-- | libs/ui/Input.cpp | 45 | ||||
-rw-r--r-- | libs/ui/InputDispatcher.cpp | 3 | ||||
-rw-r--r-- | libs/ui/InputReader.cpp | 113 | ||||
-rw-r--r-- | libs/ui/InputTransport.cpp | 38 | ||||
-rw-r--r-- | libs/ui/tests/Android.mk | 4 | ||||
-rw-r--r-- | libs/ui/tests/InputChannel_test.cpp | 158 | ||||
-rw-r--r-- | libs/ui/tests/InputDispatcher_test.cpp | 3 | ||||
-rw-r--r-- | libs/ui/tests/InputPublisherAndConsumer_test.cpp | 449 |
8 files changed, 747 insertions, 66 deletions
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp index d367708..0e6f2f5 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -50,8 +50,8 @@ void MotionEvent::initialize( int32_t action, int32_t edgeFlags, int32_t metaState, - float rawX, - float rawY, + float xOffset, + float yOffset, float xPrecision, float yPrecision, nsecs_t downTime, @@ -63,8 +63,8 @@ void MotionEvent::initialize( mAction = action; mEdgeFlags = edgeFlags; mMetaState = metaState; - mRawX = rawX; - mRawY = rawY; + mXOffset = xOffset; + mYOffset = yOffset; mXPrecision = xPrecision; mYPrecision = yPrecision; mDownTime = downTime; @@ -83,13 +83,8 @@ void MotionEvent::addSample( } void MotionEvent::offsetLocation(float xOffset, float yOffset) { - if (xOffset != 0 || yOffset != 0) { - for (size_t i = 0; i < mSamplePointerCoords.size(); i++) { - PointerCoords& pointerCoords = mSamplePointerCoords.editItemAt(i); - pointerCoords.x += xOffset; - pointerCoords.y += yOffset; - } - } + mXOffset += xOffset; + mYOffset += yOffset; } } // namespace android @@ -163,6 +158,14 @@ int64_t motion_event_get_event_time(const input_event_t* motion_event) { return reinterpret_cast<const MotionEvent*>(motion_event)->getEventTime(); } +float motion_event_get_x_offset(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getXOffset(); +} + +float motion_event_get_y_offset(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getYOffset(); +} + float motion_event_get_x_precision(const input_event_t* motion_event) { return reinterpret_cast<const MotionEvent*>(motion_event)->getXPrecision(); } @@ -179,12 +182,12 @@ int32_t motion_event_get_pointer_id(const input_event_t* motion_event, size_t po return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerId(pointer_index); } -float motion_event_get_raw_x(const input_event_t* motion_event) { - return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX(); +float motion_event_get_raw_x(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX(pointer_index); } -float motion_event_get_raw_y(const input_event_t* motion_event) { - return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY(); +float motion_event_get_raw_y(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY(pointer_index); } float motion_event_get_x(const input_event_t* motion_event, size_t pointer_index) { @@ -213,6 +216,18 @@ int64_t motion_event_get_historical_event_time(input_event_t* motion_event, history_index); } +float motion_event_get_historical_raw_x(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalRawX( + pointer_index, history_index); +} + +float motion_event_get_historical_raw_y(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalRawY( + pointer_index, history_index); +} + float motion_event_get_historical_x(input_event_t* motion_event, size_t pointer_index, size_t history_index) { return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalX( diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp index f058271..14dcada 100644 --- a/libs/ui/InputDispatcher.cpp +++ b/libs/ui/InputDispatcher.cpp @@ -379,8 +379,7 @@ void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible( mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action, entry->edgeFlags, entry->metaState, - entry->firstSample.pointerCoords[0].x, entry->firstSample.pointerCoords[0].y, - entry->xPrecision, entry->yPrecision, + 0, 0, entry->xPrecision, entry->yPrecision, entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds, entry->firstSample.pointerCoords); diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp index 62b5f28..5a280ae 100644 --- a/libs/ui/InputReader.cpp +++ b/libs/ui/InputReader.cpp @@ -19,6 +19,9 @@ // Log debug messages about pointers. #define DEBUG_POINTERS 1 +// Log debug messages about pointer assignment calculations. +#define DEBUG_POINTER_ASSIGNMENT 0 + #include <cutils/log.h> #include <ui/InputReader.h> @@ -57,6 +60,14 @@ inline static T min(const T& a, const T& b) { return a < b ? a : b; } +template<typename T> +inline static void swap(T& a, T& b) { + T temp = a; + a = b; + b = temp; +} + + int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) { int32_t mask; switch (keyCode) { @@ -188,6 +199,12 @@ void InputDevice::TouchScreenState::reset() { jumpyTouchFilter.jumpyPointsDropped = 0; } +struct PointerDistanceHeapElement { + uint32_t currentPointerIndex : 8; + uint32_t lastPointerIndex : 8; + uint64_t distance : 48; // squared distance +}; + void InputDevice::TouchScreenState::calculatePointerIds() { uint32_t currentPointerCount = currentTouch.pointerCount; uint32_t lastPointerCount = lastTouch.pointerCount; @@ -214,11 +231,7 @@ void InputDevice::TouchScreenState::calculatePointerIds() { // We build a heap of squared euclidean distances between current and last pointers // associated with the current and last pointer indices. Then, we find the best // match (by distance) for each current pointer. - struct { - uint32_t currentPointerIndex : 8; - uint32_t lastPointerIndex : 8; - uint64_t distance : 48; // squared distance - } heap[MAX_POINTERS * MAX_POINTERS]; + PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS]; uint32_t heapSize = 0; for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; @@ -233,23 +246,45 @@ void InputDevice::TouchScreenState::calculatePointerIds() { uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); // Insert new element into the heap (sift up). + heap[heapSize].currentPointerIndex = currentPointerIndex; + heap[heapSize].lastPointerIndex = lastPointerIndex; + heap[heapSize].distance = distance; heapSize += 1; - uint32_t insertionIndex = heapSize; - while (insertionIndex > 1) { - uint32_t parentIndex = (insertionIndex - 1) / 2; - if (distance < heap[parentIndex].distance) { - heap[insertionIndex] = heap[parentIndex]; - insertionIndex = parentIndex; - } else { - break; - } + } + } + + // Heapify + for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) { + startIndex -= 1; + for (uint32_t parentIndex = startIndex; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; } - heap[insertionIndex].currentPointerIndex = currentPointerIndex; - heap[insertionIndex].lastPointerIndex = lastPointerIndex; - heap[insertionIndex].distance = distance; + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; } } +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif + // Pull matches out by increasing order of distance. // To avoid reassigning pointers that have already been matched, the loop keeps track // of which last and current pointers have been matched using the matchedXXXBits variables. @@ -262,7 +297,7 @@ void InputDevice::TouchScreenState::calculatePointerIds() { for (;;) { if (first) { // The first time through the loop, we just consume the root element of - // the heap (the one with smalled distance). + // the heap (the one with smallest distance). first = false; } else { // Previous iterations consumed the root element of the heap. @@ -270,10 +305,10 @@ void InputDevice::TouchScreenState::calculatePointerIds() { heapSize -= 1; assert(heapSize > 0); - // Sift down to find where the element at index heapSize needs to be moved. - uint32_t rootIndex = 0; - for (;;) { - uint32_t childIndex = rootIndex * 2 + 1; + // Sift down. + heap[0] = heap[heapSize]; + for (uint32_t parentIndex = 0; ;) { + uint32_t childIndex = parentIndex * 2 + 1; if (childIndex >= heapSize) { break; } @@ -283,14 +318,22 @@ void InputDevice::TouchScreenState::calculatePointerIds() { childIndex += 1; } - if (heap[heapSize].distance < heap[childIndex].distance) { + if (heap[parentIndex].distance <= heap[childIndex].distance) { break; } - heap[rootIndex] = heap[childIndex]; - rootIndex = childIndex; + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; } - heap[rootIndex] = heap[heapSize]; + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif } uint32_t currentPointerIndex = heap[0].currentPointerIndex; @@ -306,6 +349,11 @@ void InputDevice::TouchScreenState::calculatePointerIds() { currentTouch.pointers[currentPointerIndex].id = id; currentTouch.idToIndex[id] = currentPointerIndex; usedIdBits.markBit(id); + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld", + lastPointerIndex, currentPointerIndex, id, heap[0].distance); +#endif break; } } @@ -320,6 +368,11 @@ void InputDevice::TouchScreenState::calculatePointerIds() { currentTouch.idToIndex[id] = currentPointerIndex; usedIdBits.markBit(id); +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - assigned: cur=%d, id=%d", + currentPointerIndex, id); +#endif + if (--i == 0) break; // done matchedCurrentBits.markBit(currentPointerIndex); } @@ -1208,8 +1261,10 @@ bool InputReader::consumeVirtualKeyTouches(nsecs_t when, int32_t x = device->touchScreen.currentTouch.pointers[0].x; int32_t y = device->touchScreen.currentTouch.pointers[0].y; - if (device->touchScreen.isPointInsideDisplay(x, y)) { - // Pointer moved inside the display area. Send key cancellation. + if (device->touchScreen.isPointInsideDisplay(x, y) + || device->touchScreen.currentTouch.pointerCount != 1) { + // Pointer moved inside the display area or another pointer also went down. + // Send key cancellation. device->touchScreen.currentVirtualKey.down = false; #if DEBUG_VIRTUAL_KEYS @@ -1227,7 +1282,7 @@ bool InputReader::consumeVirtualKeyTouches(nsecs_t when, device->touchScreen.lastTouch.clear(); return false; // not consumed } - } else if (device->touchScreen.currentTouch.pointerCount > 0 + } else if (device->touchScreen.currentTouch.pointerCount == 1 && device->touchScreen.lastTouch.pointerCount == 0) { int32_t x = device->touchScreen.currentTouch.pointers[0].x; int32_t y = device->touchScreen.currentTouch.pointers[0].y; diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp index a24180f..86bbd37 100644 --- a/libs/ui/InputTransport.cpp +++ b/libs/ui/InputTransport.cpp @@ -8,13 +8,13 @@ //#define LOG_NDEBUG 0 // Log debug messages about channel signalling (send signal, receive signal) -#define DEBUG_CHANNEL_SIGNALS 1 +#define DEBUG_CHANNEL_SIGNALS 0 // Log debug messages whenever InputChannel objects are created/destroyed -#define DEBUG_CHANNEL_LIFECYCLE 1 +#define DEBUG_CHANNEL_LIFECYCLE 0 // Log debug messages about transport actions (initialize, reset, publish, ...) -#define DEBUG_TRANSPORT_ACTIONS 1 +#define DEBUG_TRANSPORT_ACTIONS 0 #include <cutils/ashmem.h> @@ -70,7 +70,7 @@ InputChannel::~InputChannel() { } status_t InputChannel::openInputChannelPair(const String8& name, - InputChannel** outServerChannel, InputChannel** outClientChannel) { + sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { status_t result; int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE); @@ -107,12 +107,12 @@ status_t InputChannel::openInputChannelPair(const String8& name, } else { String8 serverChannelName = name; serverChannelName.append(" (server)"); - *outServerChannel = new InputChannel(serverChannelName, + outServerChannel = new InputChannel(serverChannelName, serverAshmemFd, reverse[0], forward[1]); String8 clientChannelName = name; clientChannelName.append(" (client)"); - *outClientChannel = new InputChannel(clientChannelName, + outClientChannel = new InputChannel(clientChannelName, clientAshmemFd, forward[0], reverse[1]); return OK; } @@ -125,8 +125,8 @@ status_t InputChannel::openInputChannelPair(const String8& name, ::close(serverAshmemFd); } - *outServerChannel = NULL; - *outClientChannel = NULL; + outServerChannel.clear(); + outClientChannel.clear(); return result; } @@ -155,6 +155,13 @@ status_t InputChannel::receiveSignal(char* outSignal) { return OK; } + if (nRead == 0) { // check for EOF +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ receive signal failed because peer was closed", mName.string()); +#endif + return DEAD_OBJECT; + } + if (errno == EAGAIN) { #if DEBUG_CHANNEL_SIGNALS LOGD("channel '%s' ~ receive signal failed because no signal available", mName.string()); @@ -535,13 +542,13 @@ status_t InputConsumer::initialize() { return OK; } -status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** event) { +status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) { #if DEBUG_TRANSPORT_ACTIONS LOGD("channel '%s' consumer ~ consume", mChannel->getName().string()); #endif - *event = NULL; + *outEvent = NULL; int ashmemFd = mChannel->getAshmemFd(); int result = ashmem_pin_region(ashmemFd, 0, 0); @@ -583,7 +590,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent* populateKeyEvent(keyEvent); - *event = keyEvent; + *outEvent = keyEvent; break; } @@ -593,7 +600,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent* populateMotionEvent(motionEvent); - *event = motionEvent; + *outEvent = motionEvent; break; } @@ -655,8 +662,8 @@ void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const { mSharedMessage->motion.action, mSharedMessage->motion.edgeFlags, mSharedMessage->motion.metaState, - mSharedMessage->motion.sampleData[0].coords[0].x, - mSharedMessage->motion.sampleData[0].coords[0].y, + mSharedMessage->motion.xOffset, + mSharedMessage->motion.yOffset, mSharedMessage->motion.xPrecision, mSharedMessage->motion.yPrecision, mSharedMessage->motion.downTime, @@ -676,9 +683,6 @@ void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const { motionEvent->addSample(sampleData->eventTime, sampleData->coords); } } - - motionEvent->offsetLocation(mSharedMessage->motion.xOffset, - mSharedMessage->motion.yOffset); } } // namespace android diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk index 1ff896b..46d7493 100644 --- a/libs/ui/tests/Android.mk +++ b/libs/ui/tests/Android.mk @@ -3,7 +3,9 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) test_src_files := \ - InputDispatcher_test.cpp + InputChannel_test.cpp \ + InputDispatcher_test.cpp \ + InputPublisherAndConsumer_test.cpp shared_libraries := \ libcutils \ diff --git a/libs/ui/tests/InputChannel_test.cpp b/libs/ui/tests/InputChannel_test.cpp new file mode 100644 index 0000000..6cec1c0 --- /dev/null +++ b/libs/ui/tests/InputChannel_test.cpp @@ -0,0 +1,158 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <ui/InputTransport.h> +#include <utils/Timers.h> +#include <utils/StopWatch.h> +#include <gtest/gtest.h> +#include <unistd.h> +#include <time.h> +#include <sys/mman.h> +#include <cutils/ashmem.h> + +#include "../../utils/tests/TestHelpers.h" + +namespace android { + +class InputChannelTest : public testing::Test { +protected: + virtual void SetUp() { } + virtual void TearDown() { } +}; + + +TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) { + // Our purpose here is to verify that the input channel destructor closes the + // file descriptors provided to it. One easy way is to provide it with one end + // of a pipe and to check for EPIPE on the other end after the channel is destroyed. + Pipe fakeAshmem, sendPipe, receivePipe; + + sp<InputChannel> inputChannel = new InputChannel(String8("channel name"), + fakeAshmem.sendFd, receivePipe.receiveFd, sendPipe.sendFd); + + EXPECT_STREQ("channel name", inputChannel->getName().string()) + << "channel should have provided name"; + EXPECT_EQ(fakeAshmem.sendFd, inputChannel->getAshmemFd()) + << "channel should have provided ashmem fd"; + EXPECT_EQ(receivePipe.receiveFd, inputChannel->getReceivePipeFd()) + << "channel should have provided receive pipe fd"; + EXPECT_EQ(sendPipe.sendFd, inputChannel->getSendPipeFd()) + << "channel should have provided send pipe fd"; + + inputChannel.clear(); // destroys input channel + + EXPECT_EQ(-EPIPE, fakeAshmem.readSignal()) + << "channel should have closed ashmem fd when destroyed"; + EXPECT_EQ(-EPIPE, receivePipe.writeSignal()) + << "channel should have closed receive pipe fd when destroyed"; + EXPECT_EQ(-EPIPE, sendPipe.readSignal()) + << "channel should have closed send pipe fd when destroyed"; + + // clean up fds of Pipe endpoints that were closed so we don't try to close them again + fakeAshmem.sendFd = -1; + receivePipe.receiveFd = -1; + sendPipe.sendFd = -1; +} + +TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + // Name + EXPECT_STREQ("channel name (server)", serverChannel->getName().string()) + << "server channel should have suffixed name"; + EXPECT_STREQ("channel name (client)", clientChannel->getName().string()) + << "client channel should have suffixed name"; + + // Ashmem uniqueness + EXPECT_NE(serverChannel->getAshmemFd(), clientChannel->getAshmemFd()) + << "server and client channel should have different ashmem fds because it was dup'd"; + + // Ashmem usability + ssize_t serverAshmemSize = ashmem_get_size_region(serverChannel->getAshmemFd()); + ssize_t clientAshmemSize = ashmem_get_size_region(clientChannel->getAshmemFd()); + uint32_t* serverAshmem = static_cast<uint32_t*>(mmap(NULL, serverAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, serverChannel->getAshmemFd(), 0)); + uint32_t* clientAshmem = static_cast<uint32_t*>(mmap(NULL, clientAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, clientChannel->getAshmemFd(), 0)); + ASSERT_TRUE(serverAshmem != NULL) + << "server channel ashmem should be mappable"; + ASSERT_TRUE(clientAshmem != NULL) + << "client channel ashmem should be mappable"; + *serverAshmem = 0xf00dd00d; + EXPECT_EQ(0xf00dd00d, *clientAshmem) + << "ashmem buffer should be shared by client and server"; + munmap(serverAshmem, serverAshmemSize); + munmap(clientAshmem, clientAshmemSize); + + // Server->Client communication + EXPECT_EQ(OK, serverChannel->sendSignal('S')) + << "server channel should be able to send signal to client channel"; + char signal; + EXPECT_EQ(OK, clientChannel->receiveSignal(& signal)) + << "client channel should be able to receive signal from server channel"; + EXPECT_EQ('S', signal) + << "client channel should receive the correct signal from server channel"; + + // Client->Server communication + EXPECT_EQ(OK, clientChannel->sendSignal('c')) + << "client channel should be able to send signal to server channel"; + EXPECT_EQ(OK, serverChannel->receiveSignal(& signal)) + << "server channel should be able to receive signal from client channel"; + EXPECT_EQ('c', signal) + << "server channel should receive the correct signal from client channel"; +} + +TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + char signal; + EXPECT_EQ(WOULD_BLOCK, clientChannel->receiveSignal(& signal)) + << "receiveSignal should have returned WOULD_BLOCK"; +} + +TEST_F(InputChannelTest, ReceiveSignal_WhenPeerClosed_ReturnsAnError) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + serverChannel.clear(); // close server channel + + char signal; + EXPECT_EQ(DEAD_OBJECT, clientChannel->receiveSignal(& signal)) + << "receiveSignal should have returned DEAD_OBJECT"; +} + +TEST_F(InputChannelTest, SendSignal_WhenPeerClosed_ReturnsAnError) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + serverChannel.clear(); // close server channel + + EXPECT_EQ(DEAD_OBJECT, clientChannel->sendSignal('S')) + << "sendSignal should have returned DEAD_OBJECT"; +} + + +} // namespace android diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp index 3d92043..1dc6e46 100644 --- a/libs/ui/tests/InputDispatcher_test.cpp +++ b/libs/ui/tests/InputDispatcher_test.cpp @@ -12,8 +12,7 @@ public: }; TEST_F(InputDispatcherTest, Dummy) { - SCOPED_TRACE("Trace"); - ASSERT_FALSE(true); + // TODO } } // namespace android diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp new file mode 100644 index 0000000..2d6b531 --- /dev/null +++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp @@ -0,0 +1,449 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <ui/InputTransport.h> +#include <utils/Timers.h> +#include <utils/StopWatch.h> +#include <gtest/gtest.h> +#include <unistd.h> +#include <time.h> +#include <sys/mman.h> +#include <cutils/ashmem.h> + +#include "../../utils/tests/TestHelpers.h" + +namespace android { + +class InputPublisherAndConsumerTest : public testing::Test { +protected: + sp<InputChannel> serverChannel, clientChannel; + InputPublisher* mPublisher; + InputConsumer* mConsumer; + PreallocatedInputEventFactory mEventFactory; + + virtual void SetUp() { + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + mPublisher = new InputPublisher(serverChannel); + mConsumer = new InputConsumer(clientChannel); + } + + virtual void TearDown() { + if (mPublisher) { + delete mPublisher; + mPublisher = NULL; + } + + if (mConsumer) { + delete mConsumer; + mConsumer = NULL; + } + + serverChannel.clear(); + clientChannel.clear(); + } + + void Initialize(); + void PublishAndConsumeKeyEvent(); + void PublishAndConsumeMotionEvent( + size_t samplesToAppendBeforeDispatch = 0, + size_t samplesToAppendAfterDispatch = 0); +}; + +TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) { + EXPECT_EQ(serverChannel.get(), mPublisher->getChannel().get()); + EXPECT_EQ(clientChannel.get(), mConsumer->getChannel().get()); +} + +void InputPublisherAndConsumerTest::Initialize() { + status_t status; + + status = mPublisher->initialize(); + ASSERT_EQ(OK, status) + << "publisher initialize should return OK"; + + status = mConsumer->initialize(); + ASSERT_EQ(OK, status) + << "consumer initialize should return OK"; +} + +void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() { + status_t status; + + const int32_t deviceId = 1; + const int32_t nature = INPUT_EVENT_NATURE_KEY; + const int32_t action = KEY_EVENT_ACTION_DOWN; + const int32_t flags = KEY_EVENT_FLAG_FROM_SYSTEM; + const int32_t keyCode = KEYCODE_ENTER; + const int32_t scanCode = 13; + const int32_t metaState = META_ALT_LEFT_ON | META_ALT_ON; + const int32_t repeatCount = 1; + const nsecs_t downTime = 3; + const nsecs_t eventTime = 4; + + status = mPublisher->publishKeyEvent(deviceId, nature, action, flags, + keyCode, scanCode, metaState, repeatCount, downTime, eventTime); + ASSERT_EQ(OK, status) + << "publisher publishKeyEvent should return OK"; + + status = mPublisher->sendDispatchSignal(); + ASSERT_EQ(OK, status) + << "publisher sendDispatchSignal should return OK"; + + status = mConsumer->receiveDispatchSignal(); + ASSERT_EQ(OK, status) + << "consumer receiveDispatchSignal should return OK"; + + InputEvent* event; + status = mConsumer->consume(& mEventFactory, & event); + ASSERT_EQ(OK, status) + << "consumer consume should return OK"; + + ASSERT_TRUE(event != NULL) + << "consumer should have returned non-NULL event"; + ASSERT_EQ(INPUT_EVENT_TYPE_KEY, event->getType()) + << "consumer should have returned a key event"; + + KeyEvent* keyEvent = static_cast<KeyEvent*>(event); + EXPECT_EQ(deviceId, keyEvent->getDeviceId()); + EXPECT_EQ(nature, keyEvent->getNature()); + EXPECT_EQ(action, keyEvent->getAction()); + EXPECT_EQ(flags, keyEvent->getFlags()); + EXPECT_EQ(keyCode, keyEvent->getKeyCode()); + EXPECT_EQ(scanCode, keyEvent->getScanCode()); + EXPECT_EQ(metaState, keyEvent->getMetaState()); + EXPECT_EQ(repeatCount, keyEvent->getRepeatCount()); + EXPECT_EQ(downTime, keyEvent->getDownTime()); + EXPECT_EQ(eventTime, keyEvent->getEventTime()); + + status = mConsumer->sendFinishedSignal(); + ASSERT_EQ(OK, status) + << "consumer sendFinishedSignal should return OK"; + + status = mPublisher->receiveFinishedSignal(); + ASSERT_EQ(OK, status) + << "publisher receiveFinishedSignal should return OK"; + + status = mPublisher->reset(); + ASSERT_EQ(OK, status) + << "publisher reset should return OK"; +} + +void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent( + size_t samplesToAppendBeforeDispatch, size_t samplesToAppendAfterDispatch) { + status_t status; + + const int32_t deviceId = 1; + const int32_t nature = INPUT_EVENT_NATURE_TOUCH; + const int32_t action = MOTION_EVENT_ACTION_MOVE; + const int32_t edgeFlags = MOTION_EVENT_EDGE_FLAG_TOP; + const int32_t metaState = META_ALT_LEFT_ON | META_ALT_ON; + const float xOffset = -10; + const float yOffset = -20; + const float xPrecision = 0.25; + const float yPrecision = 0.5; + const nsecs_t downTime = 3; + const size_t pointerCount = 3; + const int32_t pointerIds[pointerCount] = { 2, 0, 1 }; + + Vector<nsecs_t> sampleEventTimes; + Vector<PointerCoords> samplePointerCoords; + + for (size_t i = 0; i <= samplesToAppendAfterDispatch + samplesToAppendBeforeDispatch; i++) { + sampleEventTimes.push(i + 10); + for (size_t j = 0; j < pointerCount; j++) { + samplePointerCoords.push(); + samplePointerCoords.editTop().x = 100 * i + j; + samplePointerCoords.editTop().y = 200 * i + j; + samplePointerCoords.editTop().pressure = 0.5 * i + j; + samplePointerCoords.editTop().size = 0.7 * i + j; + } + } + + status = mPublisher->publishMotionEvent(deviceId, nature, action, edgeFlags, + metaState, xOffset, yOffset, xPrecision, yPrecision, + downTime, sampleEventTimes[0], pointerCount, pointerIds, samplePointerCoords.array()); + ASSERT_EQ(OK, status) + << "publisher publishMotionEvent should return OK"; + + for (size_t i = 0; i < samplesToAppendBeforeDispatch; i++) { + size_t sampleIndex = i + 1; + status = mPublisher->appendMotionSample(sampleEventTimes[sampleIndex], + samplePointerCoords.array() + sampleIndex * pointerCount); + ASSERT_EQ(OK, status) + << "publisher appendMotionEvent should return OK"; + } + + status = mPublisher->sendDispatchSignal(); + ASSERT_EQ(OK, status) + << "publisher sendDispatchSignal should return OK"; + + for (size_t i = 0; i < samplesToAppendAfterDispatch; i++) { + size_t sampleIndex = i + 1 + samplesToAppendBeforeDispatch; + status = mPublisher->appendMotionSample(sampleEventTimes[sampleIndex], + samplePointerCoords.array() + sampleIndex * pointerCount); + ASSERT_EQ(OK, status) + << "publisher appendMotionEvent should return OK"; + } + + status = mConsumer->receiveDispatchSignal(); + ASSERT_EQ(OK, status) + << "consumer receiveDispatchSignal should return OK"; + + InputEvent* event; + status = mConsumer->consume(& mEventFactory, & event); + ASSERT_EQ(OK, status) + << "consumer consume should return OK"; + + ASSERT_TRUE(event != NULL) + << "consumer should have returned non-NULL event"; + ASSERT_EQ(INPUT_EVENT_TYPE_MOTION, event->getType()) + << "consumer should have returned a motion event"; + + size_t lastSampleIndex = samplesToAppendBeforeDispatch + samplesToAppendAfterDispatch; + + MotionEvent* motionEvent = static_cast<MotionEvent*>(event); + EXPECT_EQ(deviceId, motionEvent->getDeviceId()); + EXPECT_EQ(nature, motionEvent->getNature()); + EXPECT_EQ(action, motionEvent->getAction()); + EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags()); + EXPECT_EQ(metaState, motionEvent->getMetaState()); + EXPECT_EQ(xPrecision, motionEvent->getXPrecision()); + EXPECT_EQ(yPrecision, motionEvent->getYPrecision()); + EXPECT_EQ(downTime, motionEvent->getDownTime()); + EXPECT_EQ(sampleEventTimes[lastSampleIndex], motionEvent->getEventTime()); + EXPECT_EQ(pointerCount, motionEvent->getPointerCount()); + EXPECT_EQ(lastSampleIndex, motionEvent->getHistorySize()); + + for (size_t i = 0; i < pointerCount; i++) { + SCOPED_TRACE(i); + EXPECT_EQ(pointerIds[i], motionEvent->getPointerId(i)); + } + + for (size_t sampleIndex = 0; sampleIndex < lastSampleIndex; sampleIndex++) { + SCOPED_TRACE(sampleIndex); + EXPECT_EQ(sampleEventTimes[sampleIndex], + motionEvent->getHistoricalEventTime(sampleIndex)); + for (size_t i = 0; i < pointerCount; i++) { + SCOPED_TRACE(i); + size_t offset = sampleIndex * pointerCount + i; + EXPECT_EQ(samplePointerCoords[offset].x, + motionEvent->getHistoricalRawX(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].y, + motionEvent->getHistoricalRawY(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].x + xOffset, + motionEvent->getHistoricalX(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].y + yOffset, + motionEvent->getHistoricalY(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].pressure, + motionEvent->getHistoricalPressure(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].size, + motionEvent->getHistoricalSize(i, sampleIndex)); + } + } + + SCOPED_TRACE(lastSampleIndex); + EXPECT_EQ(sampleEventTimes[lastSampleIndex], motionEvent->getEventTime()); + for (size_t i = 0; i < pointerCount; i++) { + SCOPED_TRACE(i); + size_t offset = lastSampleIndex * pointerCount + i; + EXPECT_EQ(samplePointerCoords[offset].x, motionEvent->getRawX(i)); + EXPECT_EQ(samplePointerCoords[offset].y, motionEvent->getRawY(i)); + EXPECT_EQ(samplePointerCoords[offset].x + xOffset, motionEvent->getX(i)); + EXPECT_EQ(samplePointerCoords[offset].y + yOffset, motionEvent->getY(i)); + EXPECT_EQ(samplePointerCoords[offset].pressure, motionEvent->getPressure(i)); + EXPECT_EQ(samplePointerCoords[offset].size, motionEvent->getSize(i)); + } + + status = mConsumer->sendFinishedSignal(); + ASSERT_EQ(OK, status) + << "consumer sendFinishedSignal should return OK"; + + status = mPublisher->receiveFinishedSignal(); + ASSERT_EQ(OK, status) + << "publisher receiveFinishedSignal should return OK"; + + status = mPublisher->reset(); + ASSERT_EQ(OK, status) + << "publisher reset should return OK"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); +} + +TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_WhenNotReset_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + status = mPublisher->publishKeyEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + ASSERT_EQ(OK, status) + << "publisher publishKeyEvent should return OK first time"; + + status = mPublisher->publishKeyEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher publishKeyEvent should return INVALID_OPERATION because " + "the publisher was not reset"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenNotReset_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = 1; + int32_t pointerIds[pointerCount] = { 0 }; + PointerCoords pointerCoords[pointerCount] = { { 0, 0, 0, 0 } }; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status) + << "publisher publishMotionEvent should return OK"; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher publishMotionEvent should return INVALID_OPERATION because "; + "the publisher was not reset"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = 0; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(BAD_VALUE, status) + << "publisher publishMotionEvent should return BAD_VALUE"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS + 1; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(BAD_VALUE, status) + << "publisher publishMotionEvent should return BAD_VALUE"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenCalledBeforeDispatchSignal_AppendsSamples) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent(3, 0)); +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenCalledAfterDispatchSignalAndNotConsumed_AppendsSamples) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent(0, 4)); +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenNoMotionEventPublished_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + PointerCoords pointerCoords[1]; + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher appendMotionSample should return INVALID_OPERATION"; +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenPublishedMotionEventIsNotAMove_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_DOWN, + 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status); + + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher appendMotionSample should return INVALID_OPERATION"; +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenAlreadyConsumed_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_MOVE, + 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status); + + status = mPublisher->sendDispatchSignal(); + ASSERT_EQ(OK, status); + + status = mConsumer->receiveDispatchSignal(); + ASSERT_EQ(OK, status); + + InputEvent* event; + status = mConsumer->consume(& mEventFactory, & event); + ASSERT_EQ(OK, status); + + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(status_t(FAILED_TRANSACTION), status) + << "publisher appendMotionSample should return FAILED_TRANSACTION"; +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenBufferFull_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_MOVE, + 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status); + + for (int count = 1;; count++) { + ASSERT_LT(count, 100000) << "should eventually reach OOM"; + + status = mPublisher->appendMotionSample(0, pointerCoords); + if (status != OK) { + ASSERT_GT(count, 12) << "should be able to add at least a dozen samples"; + ASSERT_EQ(NO_MEMORY, status) + << "publisher appendMotionSample should return NO_MEMORY when buffer is full"; + break; + } + } + + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(NO_MEMORY, status) + << "publisher appendMotionSample should return NO_MEMORY persistently until reset"; +} + +} // namespace android |