Fade out the mouse pointer after inactivity or other events.
Fades out the mouse pointer:
- after 15 seconds of inactivity normally
- after 3 seconds of inactivity in lights out mode
- after a non-modifier key down
- after a touch down
Extended the native Looper to support enqueuing time delayed
messages. This is used by the PointerController to control
pointer fade timing.
Change-Id: I87792fea7dbe2d9376c78cf354fe3189a484d9da
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 86ce098..082f11c 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -52,6 +52,14 @@
};
/*
+ * SystemUiVisibility constants from View.
+ */
+enum {
+ ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE = 0,
+ ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN = 0x00000001,
+};
+
+/*
* Maximum number of pointers supported per motion event.
* Smallest number of pointers is 1.
* (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
diff --git a/include/ui/Keyboard.h b/include/ui/Keyboard.h
index 54bc968..609f319 100644
--- a/include/ui/Keyboard.h
+++ b/include/ui/Keyboard.h
@@ -127,6 +127,11 @@
*/
extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
+/**
+ * Returns true if a key is a meta key like ALT or CAPS_LOCK.
+ */
+extern bool isMetaKey(int32_t keyCode);
+
} // namespace android
#endif // _UI_KEYBOARD_H
diff --git a/include/utils/Looper.h b/include/utils/Looper.h
index eefff31..3c2905d 100644
--- a/include/utils/Looper.h
+++ b/include/utils/Looper.h
@@ -45,6 +45,51 @@
namespace android {
/**
+ * A message that can be posted to a Looper.
+ */
+struct Message {
+ Message() : what(0) { }
+ Message(int what) : what(what) { }
+
+ /* The message type. (interpretation is left up to the handler) */
+ int what;
+};
+
+
+/**
+ * Interface for a Looper message handler.
+ *
+ * The Looper holds a strong reference to the message handler whenever it has
+ * a message to deliver to it. Make sure to call Looper::removeMessages
+ * to remove any pending messages destined for the handler so that the handler
+ * can be destroyed.
+ */
+class MessageHandler : public virtual RefBase {
+protected:
+ virtual ~MessageHandler() { }
+
+public:
+ /**
+ * Handles a message.
+ */
+ virtual void handleMessage(const Message& message) = 0;
+};
+
+
+/**
+ * A simple proxy that holds a weak reference to a message handler.
+ */
+class WeakMessageHandler : public MessageHandler {
+public:
+ WeakMessageHandler(const wp<MessageHandler>& handler);
+ virtual void handleMessage(const Message& message);
+
+private:
+ wp<MessageHandler> mHandler;
+};
+
+
+/**
* A polling loop that supports monitoring file descriptor events, optionally
* using callbacks. The implementation uses epoll() internally.
*
@@ -166,6 +211,52 @@
int removeFd(int fd);
/**
+ * Enqueues a message to be processed by the specified handler.
+ *
+ * The handler must not be null.
+ * This method can be called on any thread.
+ */
+ void sendMessage(const sp<MessageHandler>& handler, const Message& message);
+
+ /**
+ * Enqueues a message to be processed by the specified handler after all pending messages
+ * after the specified delay.
+ *
+ * The time delay is specified in uptime nanoseconds.
+ * The handler must not be null.
+ * This method can be called on any thread.
+ */
+ void sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
+ const Message& message);
+
+ /**
+ * Enqueues a message to be processed by the specified handler after all pending messages
+ * at the specified time.
+ *
+ * The time is specified in uptime nanoseconds.
+ * The handler must not be null.
+ * This method can be called on any thread.
+ */
+ void sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
+ const Message& message);
+
+ /**
+ * Removes all messages for the specified handler from the queue.
+ *
+ * The handler must not be null.
+ * This method can be called on any thread.
+ */
+ void removeMessages(const sp<MessageHandler>& handler);
+
+ /**
+ * Removes all messages of a particular type for the specified handler from the queue.
+ *
+ * The handler must not be null.
+ * This method can be called on any thread.
+ */
+ void removeMessages(const sp<MessageHandler>& handler, int what);
+
+ /**
* Prepares a looper associated with the calling thread, and returns it.
* If the thread already has a looper, it is returned. Otherwise, a new
* one is created, associated with the thread, and returned.
@@ -201,12 +292,27 @@
Request request;
};
+ struct MessageEnvelope {
+ MessageEnvelope() : uptime(0) { }
+
+ MessageEnvelope(nsecs_t uptime, const sp<MessageHandler> handler,
+ const Message& message) : uptime(uptime), handler(handler), message(message) {
+ }
+
+ nsecs_t uptime;
+ sp<MessageHandler> handler;
+ Message message;
+ };
+
const bool mAllowNonCallbacks; // immutable
int mWakeReadPipeFd; // immutable
int mWakeWritePipeFd; // immutable
Mutex mLock;
+ Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLock
+ bool mSendingMessage; // guarded by mLock
+
#ifdef LOOPER_USES_EPOLL
int mEpollFd; // immutable
@@ -256,6 +362,7 @@
// it runs on a single thread.
Vector<Response> mResponses;
size_t mResponseIndex;
+ nsecs_t mNextMessageUptime; // set to LLONG_MAX when none
int pollInner(int timeoutMillis);
void awoken();
diff --git a/libs/ui/Keyboard.cpp b/libs/ui/Keyboard.cpp
index 8b6300a..600a951 100644
--- a/libs/ui/Keyboard.cpp
+++ b/libs/ui/Keyboard.cpp
@@ -322,5 +322,26 @@
}
}
+bool isMetaKey(int32_t keyCode) {
+ switch (keyCode) {
+ case AKEYCODE_ALT_LEFT:
+ case AKEYCODE_ALT_RIGHT:
+ case AKEYCODE_SHIFT_LEFT:
+ case AKEYCODE_SHIFT_RIGHT:
+ case AKEYCODE_SYM:
+ case AKEYCODE_FUNCTION:
+ case AKEYCODE_CTRL_LEFT:
+ case AKEYCODE_CTRL_RIGHT:
+ case AKEYCODE_META_LEFT:
+ case AKEYCODE_META_RIGHT:
+ case AKEYCODE_CAPS_LOCK:
+ case AKEYCODE_NUM_LOCK:
+ case AKEYCODE_SCROLL_LOCK:
+ return true;
+ default:
+ return false;
+ }
+}
+
} // namespace android
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
index a5363d6..18f858b 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -19,10 +19,27 @@
#include <unistd.h>
#include <fcntl.h>
+#include <limits.h>
namespace android {
+// --- WeakMessageHandler ---
+
+WeakMessageHandler::WeakMessageHandler(const wp<MessageHandler>& handler) :
+ mHandler(handler) {
+}
+
+void WeakMessageHandler::handleMessage(const Message& message) {
+ sp<MessageHandler> handler = mHandler.promote();
+ if (handler != NULL) {
+ handler->handleMessage(message);
+ }
+}
+
+
+// --- Looper ---
+
#ifdef LOOPER_USES_EPOLL
// Hint for number of file descriptors to be associated with the epoll instance.
static const int EPOLL_SIZE_HINT = 8;
@@ -35,8 +52,8 @@
static pthread_key_t gTLSKey = 0;
Looper::Looper(bool allowNonCallbacks) :
- mAllowNonCallbacks(allowNonCallbacks),
- mResponseIndex(0) {
+ mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
+ mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
int result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
@@ -161,17 +178,21 @@
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
- if (! response.request.callback) {
+ ALooper_callbackFunc callback = response.request.callback;
+ if (!callback) {
+ int ident = response.request.ident;
+ int fd = response.request.fd;
+ int events = response.events;
+ void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
LOGD("%p ~ pollOnce - returning signalled identifier %d: "
- "fd=%d, events=0x%x, data=%p", this,
- response.request.ident, response.request.fd,
- response.events, response.request.data);
+ "fd=%d, events=0x%x, data=%p",
+ this, ident, fd, events, data);
#endif
- if (outFd != NULL) *outFd = response.request.fd;
- if (outEvents != NULL) *outEvents = response.events;
- if (outData != NULL) *outData = response.request.data;
- return response.request.ident;
+ if (outFd != NULL) *outFd = fd;
+ if (outEvents != NULL) *outEvents = events;
+ if (outData != NULL) *outData = data;
+ return ident;
}
}
@@ -194,6 +215,25 @@
LOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif
+ // Adjust the timeout based on when the next message is due.
+ if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (mNextMessageUptime <= now) {
+ timeoutMillis = 0;
+ } else {
+ uint64_t delay = (mNextMessageUptime - now + 999999LL) / 1000000LL;
+ if (delay < INT_MAX
+ && (timeoutMillis < 0 || int(delay) < timeoutMillis)) {
+ timeoutMillis = int(delay);
+ }
+ }
+#if DEBUG_POLL_AND_WAKE
+ LOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
+ this, mNextMessageUptime - now, timeoutMillis);
+#endif
+ }
+
+ // Poll.
int result = ALOOPER_POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
@@ -205,7 +245,6 @@
#ifdef LOOPER_USES_EPOLL
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
- bool acquiredLock = false;
#else
// Wait for wakeAndLock() waiters to run then set mPolling to true.
mLock.lock();
@@ -219,16 +258,20 @@
int eventCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);
#endif
+ // Acquire lock.
+ mLock.lock();
+
+ // Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
-
LOGW("Poll failed with an unexpected error, errno=%d", errno);
result = ALOOPER_POLL_ERROR;
goto Done;
}
+ // Check for poll timeout.
if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
LOGD("%p ~ pollOnce - timeout", this);
@@ -237,6 +280,7 @@
goto Done;
}
+ // Handle all events.
#if DEBUG_POLL_AND_WAKE
LOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif
@@ -252,11 +296,6 @@
LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
}
} else {
- if (! acquiredLock) {
- mLock.lock();
- acquiredLock = true;
- }
-
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
@@ -271,9 +310,6 @@
}
}
}
- if (acquiredLock) {
- mLock.unlock();
- }
Done: ;
#else
for (size_t i = 0; i < requestedCount; i++) {
@@ -301,15 +337,12 @@
}
}
}
-
Done:
// Set mPolling to false and wake up the wakeAndLock() waiters.
- mLock.lock();
mPolling = false;
if (mWaiters != 0) {
mAwake.broadcast();
}
- mLock.unlock();
#endif
#ifdef LOOPER_STATISTICS
@@ -335,19 +368,59 @@
}
#endif
+ // Invoke pending message callbacks.
+ mNextMessageUptime = LLONG_MAX;
+ while (mMessageEnvelopes.size() != 0) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
+ if (messageEnvelope.uptime <= now) {
+ // Remove the envelope from the list.
+ // We keep a strong reference to the handler until the call to handleMessage
+ // finishes. Then we drop it so that the handler can be deleted *before*
+ // we reacquire our lock.
+ { // obtain handler
+ sp<MessageHandler> handler = messageEnvelope.handler;
+ Message message = messageEnvelope.message;
+ mMessageEnvelopes.removeAt(0);
+ mSendingMessage = true;
+ mLock.unlock();
+
+#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
+ LOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
+ this, handler.get(), message.what);
+#endif
+ handler->handleMessage(message);
+ } // release handler
+
+ mLock.lock();
+ mSendingMessage = false;
+ result = ALOOPER_POLL_CALLBACK;
+ } else {
+ // The last message left at the head of the queue determines the next wakeup time.
+ mNextMessageUptime = messageEnvelope.uptime;
+ break;
+ }
+ }
+
+ // Release lock.
+ mLock.unlock();
+
+ // Invoke all response callbacks.
for (size_t i = 0; i < mResponses.size(); i++) {
const Response& response = mResponses.itemAt(i);
- if (response.request.callback) {
+ ALooper_callbackFunc callback = response.request.callback;
+ if (callback) {
+ int fd = response.request.fd;
+ int events = response.events;
+ void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
- LOGD("%p ~ pollOnce - invoking callback: fd=%d, events=0x%x, data=%p", this,
- response.request.fd, response.events, response.request.data);
+ LOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
+ this, callback, fd, events, data);
#endif
- int callbackResult = response.request.callback(
- response.request.fd, response.events, response.request.data);
+ int callbackResult = callback(fd, events, data);
if (callbackResult == 0) {
- removeFd(response.request.fd);
+ removeFd(fd);
}
-
result = ALOOPER_POLL_CALLBACK;
}
}
@@ -593,4 +666,83 @@
}
#endif
+void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
+ sendMessageAtTime(LLONG_MIN, handler, message);
+}
+
+void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
+ const Message& message) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ sendMessageAtTime(now + uptimeDelay, handler, message);
+}
+
+void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
+ const Message& message) {
+#if DEBUG_CALLBACKS
+ LOGD("%p ~ sendMessageAtTime - uptime=%lld, handler=%p, what=%d",
+ this, uptime, handler.get(), message.what);
+#endif
+
+ size_t i = 0;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ size_t messageCount = mMessageEnvelopes.size();
+ while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
+ i += 1;
+ }
+
+ MessageEnvelope messageEnvelope(uptime, handler, message);
+ mMessageEnvelopes.insertAt(messageEnvelope, i, 1);
+
+ // Optimization: If the Looper is currently sending a message, then we can skip
+ // the call to wake() because the next thing the Looper will do after processing
+ // messages is to decide when the next wakeup time should be. In fact, it does
+ // not even matter whether this code is running on the Looper thread.
+ if (mSendingMessage) {
+ return;
+ }
+ } // release lock
+
+ // Wake the poll loop only when we enqueue a new message at the head.
+ if (i == 0) {
+ wake();
+ }
+}
+
+void Looper::removeMessages(const sp<MessageHandler>& handler) {
+#if DEBUG_CALLBACKS
+ LOGD("%p ~ removeMessages - handler=%p", this, handler.get());
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ for (size_t i = mMessageEnvelopes.size(); i != 0; ) {
+ const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(--i);
+ if (messageEnvelope.handler == handler) {
+ mMessageEnvelopes.removeAt(i);
+ }
+ }
+ } // release lock
+}
+
+void Looper::removeMessages(const sp<MessageHandler>& handler, int what) {
+#if DEBUG_CALLBACKS
+ LOGD("%p ~ removeMessages - handler=%p, what=%d", this, handler.get(), what);
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ for (size_t i = mMessageEnvelopes.size(); i != 0; ) {
+ const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(--i);
+ if (messageEnvelope.handler == handler
+ && messageEnvelope.message.what == what) {
+ mMessageEnvelopes.removeAt(i);
+ }
+ }
+ } // release lock
+}
+
} // namespace android
diff --git a/libs/utils/tests/Looper_test.cpp b/libs/utils/tests/Looper_test.cpp
index cea1313..8bf2ba2 100644
--- a/libs/utils/tests/Looper_test.cpp
+++ b/libs/utils/tests/Looper_test.cpp
@@ -16,6 +16,13 @@
namespace android {
+enum {
+ MSG_TEST1 = 1,
+ MSG_TEST2 = 2,
+ MSG_TEST3 = 3,
+ MSG_TEST4 = 4,
+};
+
class DelayedWake : public DelayedTask {
sp<Looper> mLooper;
@@ -82,6 +89,15 @@
}
};
+class StubMessageHandler : public MessageHandler {
+public:
+ Vector<Message> messages;
+
+ virtual void handleMessage(const Message& message) {
+ messages.push(message);
+ }
+};
+
class LooperTest : public testing::Test {
protected:
sp<Looper> mLooper;
@@ -421,5 +437,257 @@
<< "replacement handler callback should be invoked";
}
+TEST_F(LooperTest, SendMessage_WhenOneMessageIsEnqueue_ShouldInvokeHandlerDuringNextPoll) {
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessage(handler, Message(MSG_TEST1));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(100);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was already sent";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+ EXPECT_EQ(size_t(1), handler->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler->messages[0].what)
+ << "handled message";
+}
+
+TEST_F(LooperTest, SendMessage_WhenMultipleMessagesAreEnqueued_ShouldInvokeHandlersInOrderDuringNextPoll) {
+ sp<StubMessageHandler> handler1 = new StubMessageHandler();
+ sp<StubMessageHandler> handler2 = new StubMessageHandler();
+ mLooper->sendMessage(handler1, Message(MSG_TEST1));
+ mLooper->sendMessage(handler2, Message(MSG_TEST2));
+ mLooper->sendMessage(handler1, Message(MSG_TEST3));
+ mLooper->sendMessage(handler1, Message(MSG_TEST4));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(1000);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was already sent";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+ EXPECT_EQ(size_t(3), handler1->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler1->messages[0].what)
+ << "handled message";
+ EXPECT_EQ(MSG_TEST3, handler1->messages[1].what)
+ << "handled message";
+ EXPECT_EQ(MSG_TEST4, handler1->messages[2].what)
+ << "handled message";
+ EXPECT_EQ(size_t(1), handler2->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST2, handler2->messages[0].what)
+ << "handled message";
+}
+
+TEST_F(LooperTest, SendMessageDelayed_WhenSentToTheFuture_ShouldInvokeHandlerAfterDelayTime) {
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessageDelayed(ms2ns(100), handler, Message(MSG_TEST1));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(1000);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "first poll should end quickly because next message timeout was computed";
+ EXPECT_EQ(ALOOPER_POLL_WAKE, result)
+ << "pollOnce result should be ALOOPER_POLL_WAKE due to wakeup";
+ EXPECT_EQ(size_t(0), handler->messages.size())
+ << "no message handled yet";
+
+ result = mLooper->pollOnce(1000);
+ elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_EQ(size_t(1), handler->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler->messages[0].what)
+ << "handled message";
+ EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "second poll should end around the time of the delayed message dispatch";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+
+ result = mLooper->pollOnce(100);
+ elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(100 + 100, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "third poll should timeout";
+ EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+ << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there were no messages left";
+}
+
+TEST_F(LooperTest, SendMessageDelayed_WhenSentToThePast_ShouldInvokeHandlerDuringNextPoll) {
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessageDelayed(ms2ns(-1000), handler, Message(MSG_TEST1));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(100);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was already sent";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+ EXPECT_EQ(size_t(1), handler->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler->messages[0].what)
+ << "handled message";
+}
+
+TEST_F(LooperTest, SendMessageDelayed_WhenSentToThePresent_ShouldInvokeHandlerDuringNextPoll) {
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessageDelayed(0, handler, Message(MSG_TEST1));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(100);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was already sent";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+ EXPECT_EQ(size_t(1), handler->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler->messages[0].what)
+ << "handled message";
+}
+
+TEST_F(LooperTest, SendMessageAtTime_WhenSentToTheFuture_ShouldInvokeHandlerAfterDelayTime) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessageAtTime(now + ms2ns(100), handler, Message(MSG_TEST1));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(1000);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "first poll should end quickly because next message timeout was computed";
+ EXPECT_EQ(ALOOPER_POLL_WAKE, result)
+ << "pollOnce result should be ALOOPER_POLL_WAKE due to wakeup";
+ EXPECT_EQ(size_t(0), handler->messages.size())
+ << "no message handled yet";
+
+ result = mLooper->pollOnce(1000);
+ elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_EQ(size_t(1), handler->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler->messages[0].what)
+ << "handled message";
+ EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "second poll should end around the time of the delayed message dispatch";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+
+ result = mLooper->pollOnce(100);
+ elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(100 + 100, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "third poll should timeout";
+ EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+ << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there were no messages left";
+}
+
+TEST_F(LooperTest, SendMessageAtTime_WhenSentToThePast_ShouldInvokeHandlerDuringNextPoll) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessageAtTime(now - ms2ns(1000), handler, Message(MSG_TEST1));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(100);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was already sent";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+ EXPECT_EQ(size_t(1), handler->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler->messages[0].what)
+ << "handled message";
+}
+
+TEST_F(LooperTest, SendMessageAtTime_WhenSentToThePresent_ShouldInvokeHandlerDuringNextPoll) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessageAtTime(now, handler, Message(MSG_TEST1));
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(100);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was already sent";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent";
+ EXPECT_EQ(size_t(1), handler->messages.size())
+ << "handled message";
+ EXPECT_EQ(MSG_TEST1, handler->messages[0].what)
+ << "handled message";
+}
+
+TEST_F(LooperTest, RemoveMessage_WhenRemovingAllMessagesForHandler_ShouldRemoveThoseMessage) {
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessage(handler, Message(MSG_TEST1));
+ mLooper->sendMessage(handler, Message(MSG_TEST2));
+ mLooper->sendMessage(handler, Message(MSG_TEST3));
+ mLooper->removeMessages(handler);
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(0);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was sent so looper was awoken";
+ EXPECT_EQ(ALOOPER_POLL_WAKE, result)
+ << "pollOnce result should be ALOOPER_POLL_WAKE because looper was awoken";
+ EXPECT_EQ(size_t(0), handler->messages.size())
+ << "no messages to handle";
+
+ result = mLooper->pollOnce(0);
+
+ EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+ << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there was nothing to do";
+ EXPECT_EQ(size_t(0), handler->messages.size())
+ << "no messages to handle";
+}
+
+TEST_F(LooperTest, RemoveMessage_WhenRemovingSomeMessagesForHandler_ShouldRemoveThoseMessage) {
+ sp<StubMessageHandler> handler = new StubMessageHandler();
+ mLooper->sendMessage(handler, Message(MSG_TEST1));
+ mLooper->sendMessage(handler, Message(MSG_TEST2));
+ mLooper->sendMessage(handler, Message(MSG_TEST3));
+ mLooper->sendMessage(handler, Message(MSG_TEST4));
+ mLooper->removeMessages(handler, MSG_TEST3);
+ mLooper->removeMessages(handler, MSG_TEST1);
+
+ StopWatch stopWatch("pollOnce");
+ int result = mLooper->pollOnce(0);
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+ << "elapsed time should approx. zero because message was sent so looper was awoken";
+ EXPECT_EQ(ALOOPER_POLL_CALLBACK, result)
+ << "pollOnce result should be ALOOPER_POLL_CALLBACK because two messages were sent";
+ EXPECT_EQ(size_t(2), handler->messages.size())
+ << "no messages to handle";
+ EXPECT_EQ(MSG_TEST2, handler->messages[0].what)
+ << "handled message";
+ EXPECT_EQ(MSG_TEST4, handler->messages[1].what)
+ << "handled message";
+
+ result = mLooper->pollOnce(0);
+
+ EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result)
+ << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there was nothing to do";
+ EXPECT_EQ(size_t(2), handler->messages.size())
+ << "no more messages to handle";
+}
} // namespace android