From 66db68948c83f1940fa66d76d28208b49bed7815 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 22 Apr 2010 18:58:52 -0700 Subject: [PATCH] Native input dispatch rewrite work in progress. The old dispatch mechanism has been left in place and continues to be used by default for now. To enable native input dispatch, edit the ENABLE_NATIVE_DISPATCH constant in WindowManagerPolicy. Includes part of the new input event NDK API. Some details TBD. To wire up input dispatch, as the ViewRoot adds a window to the window session it receives an InputChannel object as an output argument. The InputChannel encapsulates the file descriptors for a shared memory region and two pipe end-points. The ViewRoot then provides the InputChannel to the InputQueue. Behind the scenes, InputQueue simply attaches handlers to the native PollLoop object that underlies the MessageQueue. This way MessageQueue doesn't need to know anything about input dispatch per-se, it just exposes (in native code) a PollLoop that other components can use to monitor file descriptor state changes. There can be zero or more targets for any given input event. Each input target is specified by its input channel and some parameters including flags, an X/Y coordinate offset, and the dispatch timeout. An input target can request either synchronous dispatch (for foreground apps) or asynchronous dispatch (fire-and-forget for wallpapers and "outside" targets). Currently, finding the appropriate input targets for an event requires a call back into the WindowManagerServer from native code. In the future this will be refactored to avoid most of these callbacks except as required to handle pending focus transitions. End-to-end event dispatch mostly works! To do: event injection, rate limiting, ANRs, testing, optimization, etc. Change-Id: I8c36b2b9e0a2d27392040ecda0f51b636456de25 --- include/utils/BitSet.h | 67 +++++ include/utils/Buffer.h | 107 -------- include/utils/PollLoop.h | 137 ++++++++++ include/utils/Pool.h | 71 +++++ include/utils/StopWatch.h | 2 + include/utils/Vector.h | 16 ++ include/utils/VectorImpl.h | 4 +- libs/utils/Android.mk | 2 + libs/utils/PollLoop.cpp | 267 +++++++++++++++++++ libs/utils/Pool.cpp | 37 +++ libs/utils/StopWatch.cpp | 11 +- libs/utils/VectorImpl.cpp | 12 +- libs/utils/tests/Android.mk | 33 +++ libs/utils/tests/PollLoop_test.cpp | 398 +++++++++++++++++++++++++++++ libs/utils/tests/TestHelpers.h | 44 ++++ 15 files changed, 1088 insertions(+), 120 deletions(-) create mode 100644 include/utils/BitSet.h delete mode 100644 include/utils/Buffer.h create mode 100644 include/utils/PollLoop.h create mode 100644 include/utils/Pool.h create mode 100644 libs/utils/PollLoop.cpp create mode 100644 libs/utils/Pool.cpp create mode 100644 libs/utils/tests/Android.mk create mode 100644 libs/utils/tests/PollLoop_test.cpp create mode 100644 libs/utils/tests/TestHelpers.h diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h new file mode 100644 index 000000000..19c8bf093 --- /dev/null +++ b/include/utils/BitSet.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UTILS_BITSET_H +#define UTILS_BITSET_H + +#include + +/* + * Contains some bit manipulation helpers. + */ + +namespace android { + +// A simple set of 32 bits that can be individually marked or cleared. +struct BitSet32 { + uint32_t value; + + inline BitSet32() : value(0) { } + explicit inline BitSet32(uint32_t value) : value(value) { } + + // Gets the value associated with a particular bit index. + static inline uint32_t valueForBit(uint32_t n) { return 0x80000000 >> n; } + + // Clears the bit set. + inline void clear() { value = 0; } + + // Returns true if the bit set does not contain any marked bits. + inline bool isEmpty() const { return ! value; } + + // Returns true if the specified bit is marked. + inline bool hasBit(uint32_t n) const { return value & valueForBit(n); } + + // Marks the specified bit. + inline void markBit(uint32_t n) { value |= valueForBit(n); } + + // Clears the specified bit. + inline void clearBit(uint32_t n) { value &= ~ valueForBit(n); } + + // Finds the first marked bit in the set. + // Result is undefined if all bits are unmarked. + inline uint32_t firstMarkedBit() const { return __builtin_clz(value); } + + // Finds the first unmarked bit in the set. + // Result is undefined if all bits are marked. + inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); } + + inline bool operator== (const BitSet32& other) const { return value == other.value; } + inline bool operator!= (const BitSet32& other) const { return value != other.value; } +}; + +} // namespace android + +#endif // UTILS_BITSET_H diff --git a/include/utils/Buffer.h b/include/utils/Buffer.h deleted file mode 100644 index 8e22b0f21..000000000 --- a/include/utils/Buffer.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __UTILS_BUFFER_H__ -#define __UTILS_BUFFER_H__ 1 - -#include -#include -#include - -namespace android { - -class Buffer -{ -private: - char *buf; - int bufsiz; - int used; - void ensureCapacity(int len); - - void - makeRoomFor(int len) - { - if (len + used >= bufsiz) { - bufsiz = (len + used) * 3/2 + 2; - char *blah = new char[bufsiz]; - - memcpy(blah, buf, used); - delete[] buf; - buf = blah; - } - } - -public: - Buffer() - { - bufsiz = 16; - buf = new char[bufsiz]; - clear(); - } - - ~Buffer() - { - delete[] buf; - } - - void - clear() - { - buf[0] = '\0'; - used = 0; - } - - int - length() - { - return used; - } - - void - append(const char c) - { - makeRoomFor(1); - buf[used] = c; - used++; - buf[used] = '\0'; - } - - void - append(const char *s, int len) - { - makeRoomFor(len); - - memcpy(buf + used, s, len); - used += len; - buf[used] = '\0'; - } - - void - append(const char *s) - { - append(s, strlen(s)); - } - - char * - getBytes() - { - return buf; - } -}; - -}; // namespace android - -#endif diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h new file mode 100644 index 000000000..2ec39fe50 --- /dev/null +++ b/include/utils/PollLoop.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UTILS_POLL_LOOP_H +#define UTILS_POLL_LOOP_H + +#include +#include + +#include + +namespace android { + +/** + * A basic file descriptor polling loop based on poll() with callbacks. + */ +class PollLoop : public RefBase { +protected: + virtual ~PollLoop(); + +public: + PollLoop(); + + /** + * A callback that it to be invoked when an event occurs on a file descriptor. + * Specifies the events that were triggered and the user data provided when the + * callback was set. + * + * Returns true if the callback should be kept, false if it should be removed automatically + * after the callback returns. + */ + typedef bool (*Callback)(int fd, int events, void* data); + + /** + * Performs a single call to poll() with optional timeout in milliseconds. + * Invokes callbacks for all file descriptors on which an event occurred. + * + * If the timeout is zero, returns immediately without blocking. + * If the timeout is negative, waits indefinitely until awoken. + * + * Returns true if a callback was invoked or if the loop was awoken by wake(). + * Returns false if a timeout or error occurred. + * + * This method must only be called on the main thread. + * This method blocks until either a file descriptor is signalled, a timeout occurs, + * or wake() is called. + * This method does not return until it has finished invoking the appropriate callbacks + * for all file descriptors that were signalled. + */ + bool pollOnce(int timeoutMillis); + + /** + * Wakes the loop asynchronously. + * + * This method can be called on any thread. + * This method returns immediately. + */ + void wake(); + + /** + * Sets the callback for a file descriptor, replacing the existing one, if any. + * It is an error to call this method with events == 0 or callback == NULL. + * + * Note that a callback can be invoked with the POLLERR, POLLHUP or POLLNVAL events + * even if it is not explicitly requested when registered. + * + * This method can be called on any thread. + * This method may block briefly if it needs to wake the poll loop. + */ + void setCallback(int fd, int events, Callback callback, void* data = NULL); + + /** + * Removes the callback for a file descriptor, if one exists. + * + * When this method returns, it is safe to close the file descriptor since the poll loop + * will no longer have a reference to it. However, it is possible for the callback to + * already be running or for it to run one last time if the file descriptor was already + * signalled. Calling code is responsible for ensuring that this case is safely handled. + * For example, if the callback takes care of removing itself during its own execution either + * by returning false or calling this method, then it can be guaranteed to not be invoked + * again at any later time unless registered anew. + * + * This method can be called on any thread. + * This method may block briefly if it needs to wake the poll loop. + * + * Returns true if a callback was actually removed, false if none was registered. + */ + bool removeCallback(int fd); + +private: + struct RequestedCallback { + Callback callback; + void* data; + }; + + struct PendingCallback { + int fd; + int events; + Callback callback; + void* data; + }; + + Mutex mLock; + Condition mAwake; + bool mPolling; + + int mWakeReadPipeFd; + int mWakeWritePipeFd; + + Vector mRequestedFds; + Vector mRequestedCallbacks; + + Vector mPendingCallbacks; // used privately by pollOnce + + void openWakePipe(); + void closeWakePipe(); + + ssize_t getRequestIndexLocked(int fd); + void wakeAndLock(); +}; + +} // namespace android + +#endif // UTILS_POLL_LOOP_H diff --git a/include/utils/Pool.h b/include/utils/Pool.h new file mode 100644 index 000000000..2ee768eef --- /dev/null +++ b/include/utils/Pool.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UTILS_POOL_H +#define UTILS_POOL_H + +#include + +namespace android { + +class PoolImpl { +public: + PoolImpl(size_t objSize); + ~PoolImpl(); + + void* allocImpl(); + void freeImpl(void* obj); + +private: + size_t mObjSize; +}; + +/* + * A homogeneous typed memory pool for fixed size objects. + * Not intended to be thread-safe. + */ +template +class Pool : private PoolImpl { +public: + /* Creates an initially empty pool. */ + Pool() : PoolImpl(sizeof(T)) { } + + /* Destroys the pool. + * Assumes that the pool is empty. */ + ~Pool() { } + + /* Allocates an object from the pool, growing the pool if needed. */ + inline T* alloc() { + void* mem = allocImpl(); + if (! traits::has_trivial_ctor) { + return new (mem) T(); + } else { + return static_cast(mem); + } + } + + /* Frees an object from the pool. */ + inline void free(T* obj) { + if (! traits::has_trivial_dtor) { + obj->~T(); + } + freeImpl(obj); + } +}; + +} // namespace android + +#endif // UTILS_POOL_H diff --git a/include/utils/StopWatch.h b/include/utils/StopWatch.h index cc0bebc40..693dd3ccf 100644 --- a/include/utils/StopWatch.h +++ b/include/utils/StopWatch.h @@ -37,6 +37,8 @@ public: const char* name() const; nsecs_t lap(); nsecs_t elapsedTime() const; + + void reset(); private: const char* mName; diff --git a/include/utils/Vector.h b/include/utils/Vector.h index ad59fd63e..d40ae1664 100644 --- a/include/utils/Vector.h +++ b/include/utils/Vector.h @@ -114,6 +114,12 @@ public: ssize_t appendVector(const Vector& vector); + //! insert an array at a given index + ssize_t insertArrayAt(const TYPE* array, size_t index, size_t numItems); + + //! append an array at the end of this vector + ssize_t appendArray(const TYPE* array, size_t numItems); + /*! * add/insert/replace items */ @@ -258,6 +264,16 @@ ssize_t Vector::appendVector(const Vector& vector) { return VectorImpl::appendVector(reinterpret_cast(vector)); } +template inline +ssize_t Vector::insertArrayAt(const TYPE* array, size_t index, size_t numItems) { + return VectorImpl::insertAt(array, index, numItems); +} + +template inline +ssize_t Vector::appendArray(const TYPE* array, size_t numItems) { + return VectorImpl::add(array, numItems); +} + template inline ssize_t Vector::insertAt(const TYPE& item, size_t index, size_t numItems) { return VectorImpl::insertAt(&item, index, numItems); diff --git a/include/utils/VectorImpl.h b/include/utils/VectorImpl.h index 49b03f10b..46a7bc265 100644 --- a/include/utils/VectorImpl.h +++ b/include/utils/VectorImpl.h @@ -76,7 +76,7 @@ public: void push(); void push(const void* item); ssize_t add(); - ssize_t add(const void* item); + ssize_t add(const void* item, size_t numItems = 1); ssize_t replaceAt(size_t index); ssize_t replaceAt(const void* item, size_t index); @@ -184,6 +184,8 @@ private: void push(const void* item); ssize_t insertVectorAt(const VectorImpl& vector, size_t index); ssize_t appendVector(const VectorImpl& vector); + ssize_t insertArrayAt(const void* array, size_t index, size_t numItems); + ssize_t appendArray(const void* array, size_t numItems); ssize_t insertAt(size_t where, size_t numItems = 1); ssize_t insertAt(const void* item, size_t where, size_t numItems = 1); ssize_t replaceAt(size_t index); diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index afecdcb0c..945b03958 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -26,6 +26,8 @@ commonSources:= \ Debug.cpp \ FileMap.cpp \ Flattenable.cpp \ + PollLoop.cpp \ + Pool.cpp \ RefBase.cpp \ ResourceTypes.cpp \ SharedBuffer.cpp \ diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp new file mode 100644 index 000000000..90a3e8b35 --- /dev/null +++ b/libs/utils/PollLoop.cpp @@ -0,0 +1,267 @@ +// +// Copyright 2010 The Android Open Source Project +// +// A select loop implementation. +// +#define LOG_TAG "PollLoop" + +//#define LOG_NDEBUG 0 + +// Debugs poll and wake interactions. +#define DEBUG_POLL_AND_WAKE 0 + +// Debugs callback registration and invocation. +#define DEBUG_CALLBACKS 1 + +#include +#include + +#include +#include + +namespace android { + +PollLoop::PollLoop() : + mPolling(false) { + openWakePipe(); +} + +PollLoop::~PollLoop() { + closeWakePipe(); +} + +void PollLoop::openWakePipe() { + int wakeFds[2]; + int result = pipe(wakeFds); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); + + mWakeReadPipeFd = wakeFds[0]; + mWakeWritePipeFd = wakeFds[1]; + + result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d", + errno); + + result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d", + errno); + + // Add the wake pipe to the head of the request list with a null callback. + struct pollfd requestedFd; + requestedFd.fd = mWakeReadPipeFd; + requestedFd.events = POLLIN; + mRequestedFds.insertAt(requestedFd, 0); + + RequestedCallback requestedCallback; + requestedCallback.callback = NULL; + requestedCallback.data = NULL; + mRequestedCallbacks.insertAt(requestedCallback, 0); +} + +void PollLoop::closeWakePipe() { + close(mWakeReadPipeFd); + close(mWakeWritePipeFd); + + // Note: We don't need to remove the poll structure or callback entry because this + // method is currently only called by the destructor. +} + +bool PollLoop::pollOnce(int timeoutMillis) { + mLock.lock(); + mPolling = true; + mLock.unlock(); + + bool result; + size_t requestedCount = mRequestedFds.size(); + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - waiting on %d fds", this, requestedCount); + for (size_t i = 0; i < requestedCount; i++) { + LOGD(" fd %d - events %d", mRequestedFds[i].fd, mRequestedFds[i].events); + } +#endif + + int respondedCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis); + + if (respondedCount == 0) { + // Timeout +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - timeout", this); +#endif + result = false; + goto Done; + } + + if (respondedCount < 0) { + // Error +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - error, errno=%d", this, errno); +#endif + if (errno != EINTR) { + LOGW("Poll failed with an unexpected error, errno=%d", errno); + } + result = false; + goto Done; + } + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - handling responses from %d fds", this, respondedCount); + for (size_t i = 0; i < requestedCount; i++) { + LOGD(" fd %d - events %d, revents %d", mRequestedFds[i].fd, mRequestedFds[i].events, + mRequestedFds[i].revents); + } +#endif + + mPendingCallbacks.clear(); + for (size_t i = 0; i < requestedCount; i++) { + const struct pollfd& requestedFd = mRequestedFds.itemAt(i); + + short revents = requestedFd.revents; + if (revents) { + const RequestedCallback& requestedCallback = mRequestedCallbacks.itemAt(i); + Callback callback = requestedCallback.callback; + + if (callback) { + PendingCallback pendingCallback; + pendingCallback.fd = requestedFd.fd; + pendingCallback.events = requestedFd.revents; + pendingCallback.callback = callback; + pendingCallback.data = requestedCallback.data; + mPendingCallbacks.push(pendingCallback); + } else { + if (requestedFd.fd == mWakeReadPipeFd) { +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - awoken", this); +#endif + char buffer[16]; + ssize_t nRead; + do { + nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); + } while (nRead == sizeof(buffer)); + } else { +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + LOGD("%p ~ pollOnce - fd %d has no callback!", this, requestedFd.fd); +#endif + } + } + + respondedCount -= 1; + if (respondedCount == 0) { + break; + } + } + } + result = true; + +Done: + mLock.lock(); + mPolling = false; + mAwake.broadcast(); + mLock.unlock(); + + if (result) { + size_t pendingCount = mPendingCallbacks.size(); + for (size_t i = 0; i < pendingCount; i++) { + const PendingCallback& pendingCallback = mPendingCallbacks.itemAt(i); +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + LOGD("%p ~ pollOnce - invoking callback for fd %d", this, pendingCallback.fd); +#endif + + bool keep = pendingCallback.callback(pendingCallback.fd, pendingCallback.events, + pendingCallback.data); + if (! keep) { + removeCallback(pendingCallback.fd); + } + } + } + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - done", this); +#endif + return result; +} + +void PollLoop::wake() { +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ wake", this); +#endif + + ssize_t nWrite = write(mWakeWritePipeFd, "W", 1); + if (nWrite != 1) { + if (errno != EAGAIN) { + LOGW("Could not write wake signal, errno=%d", errno); + } + } +} + +void PollLoop::setCallback(int fd, int events, Callback callback, void* data) { +#if DEBUG_CALLBACKS + LOGD("%p ~ setCallback - fd=%d, events=%d", this, fd, events); +#endif + + if (! events || ! callback) { + LOGE("Invalid attempt to set a callback with no selected poll events or no callback."); + removeCallback(fd); + return; + } + + wakeAndLock(); + + struct pollfd requestedFd; + requestedFd.fd = fd; + requestedFd.events = events; + + RequestedCallback requestedCallback; + requestedCallback.callback = callback; + requestedCallback.data = data; + + ssize_t index = getRequestIndexLocked(fd); + if (index < 0) { + mRequestedFds.push(requestedFd); + mRequestedCallbacks.push(requestedCallback); + } else { + mRequestedFds.replaceAt(requestedFd, size_t(index)); + mRequestedCallbacks.replaceAt(requestedCallback, size_t(index)); + } + + mLock.unlock(); +} + +bool PollLoop::removeCallback(int fd) { +#if DEBUG_CALLBACKS + LOGD("%p ~ removeCallback - fd=%d", this, fd); +#endif + + wakeAndLock(); + + ssize_t index = getRequestIndexLocked(fd); + if (index >= 0) { + mRequestedFds.removeAt(size_t(index)); + mRequestedCallbacks.removeAt(size_t(index)); + } + + mLock.unlock(); + return index >= 0; +} + +ssize_t PollLoop::getRequestIndexLocked(int fd) { + size_t requestCount = mRequestedFds.size(); + + for (size_t i = 0; i < requestCount; i++) { + if (mRequestedFds.itemAt(i).fd == fd) { + return i; + } + } + + return -1; +} + +void PollLoop::wakeAndLock() { + mLock.lock(); + while (mPolling) { + wake(); + mAwake.wait(mLock); + } +} + +} // namespace android diff --git a/libs/utils/Pool.cpp b/libs/utils/Pool.cpp new file mode 100644 index 000000000..8f18cb913 --- /dev/null +++ b/libs/utils/Pool.cpp @@ -0,0 +1,37 @@ +// +// Copyright 2010 The Android Open Source Project +// +// A simple memory pool. +// +#define LOG_TAG "Pool" + +//#define LOG_NDEBUG 0 + +#include +#include + +#include + +namespace android { + +// TODO Provide a real implementation of a pool. This is just a stub for initial development. + +PoolImpl::PoolImpl(size_t objSize) : + mObjSize(objSize) { +} + +PoolImpl::~PoolImpl() { +} + +void* PoolImpl::allocImpl() { + void* ptr = malloc(mObjSize); + LOG_ALWAYS_FATAL_IF(ptr == NULL, "Cannot allocate new pool object."); + return ptr; +} + +void PoolImpl::freeImpl(void* obj) { + LOG_ALWAYS_FATAL_IF(obj == NULL, "Caller attempted to free NULL pool object."); + return free(obj); +} + +} // namespace android diff --git a/libs/utils/StopWatch.cpp b/libs/utils/StopWatch.cpp index 68a1c5217..b5dda2fac 100644 --- a/libs/utils/StopWatch.cpp +++ b/libs/utils/StopWatch.cpp @@ -30,10 +30,9 @@ namespace android { StopWatch::StopWatch(const char *name, int clock, uint32_t flags) - : mName(name), mClock(clock), mFlags(flags), - mStartTime(0), mNumLaps(0) + : mName(name), mClock(clock), mFlags(flags) { - mStartTime = systemTime(mClock); + reset(); } StopWatch::~StopWatch() @@ -72,6 +71,12 @@ nsecs_t StopWatch::elapsedTime() const return systemTime(mClock) - mStartTime; } +void StopWatch::reset() +{ + mNumLaps = 0; + mStartTime = systemTime(mClock); +} + /*****************************************************************************/ diff --git a/libs/utils/VectorImpl.cpp b/libs/utils/VectorImpl.cpp index 0322af7da..b09c6cae7 100644 --- a/libs/utils/VectorImpl.cpp +++ b/libs/utils/VectorImpl.cpp @@ -108,13 +108,7 @@ size_t VectorImpl::capacity() const ssize_t VectorImpl::insertVectorAt(const VectorImpl& vector, size_t index) { - if (index > size()) - return BAD_INDEX; - void* where = _grow(index, vector.size()); - if (where) { - _do_copy(where, vector.arrayImpl(), vector.size()); - } - return where ? index : (ssize_t)NO_MEMORY; + return insertAt(vector.arrayImpl(), index, vector.size()); } ssize_t VectorImpl::appendVector(const VectorImpl& vector) @@ -226,9 +220,9 @@ ssize_t VectorImpl::add() return add(0); } -ssize_t VectorImpl::add(const void* item) +ssize_t VectorImpl::add(const void* item, size_t numItems) { - return insertAt(item, size()); + return insertAt(item, size(), numItems); } ssize_t VectorImpl::replaceAt(size_t index) diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk new file mode 100644 index 000000000..45e8061af --- /dev/null +++ b/libs/utils/tests/Android.mk @@ -0,0 +1,33 @@ +# Build the unit tests. +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +test_src_files := \ + PollLoop_test.cpp + +LOCAL_SHARED_LIBRARIES := \ + libz \ + liblog \ + libcutils \ + libutils \ + libstlport + +LOCAL_STATIC_LIBRARIES := \ + libgtest \ + libgtest_main + +LOCAL_C_INCLUDES := \ + external/zlib \ + external/icu4c/common \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport + +LOCAL_MODULE_TAGS := eng tests + +$(foreach file,$(test_src_files), \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) diff --git a/libs/utils/tests/PollLoop_test.cpp b/libs/utils/tests/PollLoop_test.cpp new file mode 100644 index 000000000..6c719c85c --- /dev/null +++ b/libs/utils/tests/PollLoop_test.cpp @@ -0,0 +1,398 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include +#include +#include +#include +#include +#include + +#include "TestHelpers.h" + +// # of milliseconds to fudge stopwatch measurements +#define TIMING_TOLERANCE_MS 25 + +namespace android { + +class Pipe { +public: + int sendFd; + int receiveFd; + + Pipe() { + int fds[2]; + ::pipe(fds); + + receiveFd = fds[0]; + sendFd = fds[1]; + } + + ~Pipe() { + ::close(sendFd); + ::close(receiveFd); + } + + bool writeSignal() { + return ::write(sendFd, "*", 1) == 1; + } + + bool readSignal() { + char buf[1]; + return ::read(receiveFd, buf, 1) == 1; + } +}; + +class DelayedWake : public DelayedTask { + sp mPollLoop; + +public: + DelayedWake(int delayMillis, const sp pollLoop) : + DelayedTask(delayMillis), mPollLoop(pollLoop) { + } + +protected: + virtual void doTask() { + mPollLoop->wake(); + } +}; + +class DelayedWriteSignal : public DelayedTask { + Pipe* mPipe; + +public: + DelayedWriteSignal(int delayMillis, Pipe* pipe) : + DelayedTask(delayMillis), mPipe(pipe) { + } + +protected: + virtual void doTask() { + mPipe->writeSignal(); + } +}; + +class CallbackHandler { +public: + void setCallback(const sp& pollLoop, int fd, int events) { + pollLoop->setCallback(fd, events, staticHandler, this); + } + +protected: + virtual ~CallbackHandler() { } + + virtual bool handler(int fd, int events) = 0; + +private: + static bool staticHandler(int fd, int events, void* data) { + return static_cast(data)->handler(fd, events); + } +}; + +class StubCallbackHandler : public CallbackHandler { +public: + bool nextResult; + int callbackCount; + + int fd; + int events; + + StubCallbackHandler(bool nextResult) : nextResult(nextResult), + callbackCount(0), fd(-1), events(-1) { + } + +protected: + virtual bool handler(int fd, int events) { + callbackCount += 1; + this->fd = fd; + this->events = events; + return nextResult; + } +}; + +class PollLoopTest : public testing::Test { +protected: + sp mPollLoop; + + virtual void SetUp() { + mPollLoop = new PollLoop(); + } + + virtual void TearDown() { + mPollLoop.clear(); + } +}; + + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeoutAndReturnsFalse) { + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturnsTrue) { + mPollLoop->wake(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because wake() was called before waiting"; + EXPECT_TRUE(result) + << "pollOnce result should be true because loop was awoken"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturnsTrue) { + sp delayedWake = new DelayedWake(100, mPollLoop); + delayedWake->run(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal wake delay"; + EXPECT_TRUE(result) + << "pollOnce result should be true because loop was awoken"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturnsFalse) { + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturnsFalse) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + + ASSERT_TRUE(pipe.writeSignal()); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturnsFalse) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + + pipe.writeSignal(); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + sp delayedWriteSignal = new DelayedWriteSignal(100, & pipe); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + delayedWriteSignal->run(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal signal delay"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + pipe.writeSignal(); // would cause FD to be considered signalled + mPollLoop->removeCallback(pipe.receiveFd); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout because FD was no longer registered"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not be invoked"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) { + Pipe pipe; + StubCallbackHandler handler(false); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + // First loop: Callback is registered and FD is signalled. + pipe.writeSignal(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because FD was already signalled"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked"; + + // Second loop: Callback is no longer registered and FD is signalled. + pipe.writeSignal(); + + stopWatch.reset(); + result = mPollLoop->pollOnce(0); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because timeout was zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should not be invoked this time"; +} + +TEST_F(PollLoopTest, RemoveCallback_WhenCallbackNotAdded_ReturnsFalse) { + bool result = mPollLoop->removeCallback(1); + + EXPECT_FALSE(result) + << "removeCallback should return false because FD not registered"; +} + +TEST_F(PollLoopTest, RemoveCallback_WhenCallbackAddedThenRemovedTwice_ReturnsTrueFirstTimeAndReturnsFalseSecondTime) { + Pipe pipe; + StubCallbackHandler handler(false); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + // First time. + bool result = mPollLoop->removeCallback(pipe.receiveFd); + + EXPECT_TRUE(result) + << "removeCallback should return true first time because FD was registered"; + + // Second time. + result = mPollLoop->removeCallback(pipe.receiveFd); + + EXPECT_FALSE(result) + << "removeCallback should return false second time because FD was no longer registered"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) { + Pipe pipe; + StubCallbackHandler handler1(true); + StubCallbackHandler handler2(true); + + handler1.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + handler2.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); // replace it + pipe.writeSignal(); // would cause FD to be considered signalled + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because FD was already signalled"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(0, handler1.callbackCount) + << "original handler callback should not be invoked because it was replaced"; + EXPECT_EQ(1, handler2.callbackCount) + << "replacement handler callback should be invoked"; +} + + +} // namespace android diff --git a/libs/utils/tests/TestHelpers.h b/libs/utils/tests/TestHelpers.h new file mode 100644 index 000000000..e55af3ccd --- /dev/null +++ b/libs/utils/tests/TestHelpers.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TESTHELPERS_H +#define TESTHELPERS_H + +#include + +namespace android { + +class DelayedTask : public Thread { + int mDelayMillis; + +public: + DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { } + +protected: + virtual ~DelayedTask() { } + + virtual void doTask() = 0; + + virtual bool threadLoop() { + usleep(mDelayMillis * 1000); + doTask(); + return false; + } +}; + +} // namespace android + +#endif // TESTHELPERS_H