blob: 0c051bf5623c6d7afb80a9b2abc86b79fc5aef0d [file] [log] [blame]
/*
* 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.
*/
package android.os.cts;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.system.ErrnoException;
import android.system.Os;
import android.os.SystemClock;
import android.os.MessageQueue.IdleHandler;
import android.test.AndroidTestCase;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class MessageQueueTest extends AndroidTestCase {
private static final long TIMEOUT = 1000;
public void testAddIdleHandler() throws InterruptedException {
TestLooperThread looperThread = new TestLooperThread(Test.ADD_IDLE_HANDLER);
looperThread.start();
try {
if (!looperThread.hasIdleHandlerBeenCalled()) {
fail("IdleHandler#queueIdle was NOT called: " + looperThread.getTestProgress());
}
} finally {
assertTrue("The looper should have been running.", looperThread.quit());
}
}
public void testRemoveIdleHandler() throws InterruptedException {
TestLooperThread looperThread = new TestLooperThread(Test.REMOVE_IDLE_HANDLER);
looperThread.start();
try {
if (looperThread.hasIdleHandlerBeenCalled()) {
fail("IdleHandler#queueIdle was called: " + looperThread.getTestProgress());
}
} finally {
assertTrue("The looper should have been running.", looperThread.quit());
}
}
private enum Test {ADD_IDLE_HANDLER, REMOVE_IDLE_HANDLER}
/**
* {@link HandlerThread} that adds or removes an idle handler depending on the {@link Test}
* given. It uses a {@link CountDownLatch} with an initial count of 2. The first count down
* occurs right before the looper's run thread had started running. The final count down
* occurs when the idle handler was executed. Tests can call {@link #hasIdleHandlerBeenCalled()}
* to see if the countdown reached to 0 or not.
*/
private static class TestLooperThread extends HandlerThread {
private final Test mTestMode;
private final CountDownLatch mIdleLatch = new CountDownLatch(2);
TestLooperThread(Test testMode) {
super("TestLooperThread");
mTestMode = testMode;
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
IdleHandler idleHandler = new IdleHandler() {
@Override
public boolean queueIdle() {
mIdleLatch.countDown();
return false;
}
};
if (mTestMode == Test.ADD_IDLE_HANDLER) {
Looper.myQueue().addIdleHandler(idleHandler);
} else {
Looper.myQueue().addIdleHandler(idleHandler);
Looper.myQueue().removeIdleHandler(idleHandler);
}
}
@Override
public void run() {
mIdleLatch.countDown();
super.run();
}
public boolean hasIdleHandlerBeenCalled() throws InterruptedException {
return mIdleLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
}
public long getTestProgress() {
return mIdleLatch.getCount();
}
}
public void testIsIdle() throws Exception {
HandlerThread thread = new HandlerThread("testIsIdle");
thread.start();
try {
// Queue should initially be idle.
assertTrue(thread.getLooper().getQueue().isIdle());
// Post two messages. Block in the first one leaving the second one pending.
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(1);
Handler handler = new Handler(thread.getLooper());
handler.post(new Runnable() {
@Override
public void run() {
// Wait for latch1 released before returning.
try {
latch1.await(TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) { }
}
});
handler.post(new Runnable() {
@Override
public void run() {
// Release latch2 when finished.
latch2.countDown();
}
});
// The first message is blocked so the second should still be in the queue.
// At this point the queue will not be idle because there is a pending message.
assertFalse(thread.getLooper().getQueue().isIdle());
// Let the first message complete and wait for the second to leave the queue.
// At this point the queue will be idle because it is empty.
latch1.countDown();
latch2.await(TIMEOUT, TimeUnit.MILLISECONDS);
assertTrue(thread.getLooper().getQueue().isIdle());
} finally {
thread.quitSafely();
}
}
/**
* Use MessageQueue, send message by order
*/
public void testMessageOrder() throws Exception {
OrderTestHelper tester = new OrderTestHelper() {
@Override
public void init() {
super.init();
long now = SystemClock.uptimeMillis() + 200;
mLastMessage = 4;
mHandler.sendMessageAtTime(mHandler.obtainMessage(2), now + 1);
mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now + 2);
mHandler.sendMessageAtTime(mHandler.obtainMessage(4), now + 2);
mHandler.sendMessageAtTime(mHandler.obtainMessage(0), now + 0);
mHandler.sendMessageAtTime(mHandler.obtainMessage(1), now + 0);
}
};
tester.doTest(1000, 50);
}
/**
* Use MessageQueue, send message at front of queue.
*/
public void testAtFrontOfQueue() throws Exception {
OrderTestHelper tester = new OrderTestHelper() {
@Override
public void init() {
super.init();
long now = SystemClock.uptimeMillis() + 200;
mLastMessage = 3;
mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now);
mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(2));
mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(0));
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0) {
mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(1));
}
}
};
tester.doTest(1000, 50);
}
public void testRegisterFileDescriptorCallbackThrowsWhenFdIsNull() {
MessageQueue queue = Looper.getMainLooper().getQueue();
try {
queue.addOnFileDescriptorEventListener(null, 0,
new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
return 0;
}
});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected
}
}
public void testRegisterFileDescriptorCallbackThrowsWhenCallbackIsNull() throws Exception {
MessageQueue queue = Looper.getMainLooper().getQueue();
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
try (ParcelFileDescriptor reader = pipe[0];
ParcelFileDescriptor writer = pipe[1]) {
try {
queue.addOnFileDescriptorEventListener(reader.getFileDescriptor(), 0, null);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected
}
}
}
public void testUnregisterFileDescriptorCallbackThrowsWhenFdIsNull() throws Exception {
MessageQueue queue = Looper.getMainLooper().getQueue();
try {
queue.removeOnFileDescriptorEventListener(null);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected
}
}
public void testUnregisterFileDescriptorCallbackDoesNothingWhenFdNotRegistered()
throws Exception {
MessageQueue queue = Looper.getMainLooper().getQueue();
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
try (ParcelFileDescriptor reader = pipe[0];
ParcelFileDescriptor writer = pipe[1]) {
queue.removeOnFileDescriptorEventListener(reader.getFileDescriptor());
}
}
public void testFileDescriptorCallbacks() throws Throwable {
// Prepare a special looper that we can catch exceptions from.
AssertableHandlerThread thread = new AssertableHandlerThread();
thread.start();
try {
final CountDownLatch writerSawError = new CountDownLatch(1);
final CountDownLatch readerDone = new CountDownLatch(1);
final MessageQueue queue = thread.getLooper().getQueue();
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
try (final FileInputStream reader = new AutoCloseInputStream(pipe[0]);
final FileOutputStream writer = new AutoCloseOutputStream(pipe[1])) {
final int size = 256 * 1024;
// Prepare to write a lot of data to the pipe asynchronously.
// We don't actually care about the content (assume pipes work correctly)
// so we just write lots of zeros.
OnFileDescriptorEventListener writerCallback = new OnFileDescriptorEventListener() {
private byte[] mBuffer = new byte[4096];
private int mRemaining = size;
private boolean mDone;
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
assertEquals(pipe[1].getFileDescriptor(), fd);
if (!mDone) {
// When an error happens because the reader closed its end,
// signal the test, and remove the callback.
if ((events & OnFileDescriptorEventListener.EVENT_ERROR) != 0) {
writerSawError.countDown();
mDone = true;
return 0;
}
// Write all output until an error is observed.
if ((events & OnFileDescriptorEventListener.EVENT_OUTPUT) != 0) {
int count = Math.min(mBuffer.length, mRemaining);
try {
writer.write(mBuffer, 0, count);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
mRemaining -= count;
return mRemaining != 0 ? EVENT_OUTPUT : EVENT_ERROR;
}
}
// Should never see anything else.
fail("Saw unexpected events: " + events + ", mDone=" + mDone);
return 0;
}
};
// Prepare to read all of that data.
OnFileDescriptorEventListener readerCallback = new OnFileDescriptorEventListener() {
private byte[] mBuffer = new byte[4096];
private int mRemaining = size;
private boolean mDone;
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
assertEquals(pipe[0].getFileDescriptor(), fd);
if (!mDone) {
// Errors should not happen.
if ((events & OnFileDescriptorEventListener.EVENT_ERROR) != 0) {
fail("Saw unexpected error.");
return 0;
}
// Read until everything is read, signal the test,
// and remove the callback.
if ((events & OnFileDescriptorEventListener.EVENT_INPUT) != 0) {
try {
int count = reader.read(mBuffer, 0, mBuffer.length);
mRemaining -= count;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
if (mRemaining != 0) {
return EVENT_INPUT;
}
readerDone.countDown();
mDone = true;
return 0;
}
}
// Should never see anything else.
fail("Saw unexpected events: " + events + ", mDone=" + mDone);
return 0;
}
};
// Register the callbacks.
queue.addOnFileDescriptorEventListener(reader.getFD(),
OnFileDescriptorEventListener.EVENT_INPUT, readerCallback);
queue.addOnFileDescriptorEventListener(writer.getFD(),
OnFileDescriptorEventListener.EVENT_OUTPUT, writerCallback);
// Wait for the reader to see all of the data that the writer
// is prepared to send.
readerDone.await(TIMEOUT, TimeUnit.MILLISECONDS);
// At this point the reader's callback should be unregistered.
// Close the reader's file descriptor (pretend it crashed or something).
reader.close();
// Because the reader is gone, the writer should observe an error (EPIPE).
// Wait for this to happen.
writerSawError.await(TIMEOUT, TimeUnit.MILLISECONDS);
// The reader and writer should already be unregistered.
// Try to unregistered them again to ensure nothing bad happens.
queue.removeOnFileDescriptorEventListener(reader.getFD());
queue.removeOnFileDescriptorEventListener(writer.getFD());
}
} finally {
thread.quitAndRethrow();
}
}
/**
* Since file descriptor numbers may be reused, there are some interesting
* edge cases around closing file descriptors within the callback and adding
* new ones with the same number.
*
* Register a file descriptor, close it from within the callback, then return.
* Later, create a new file descriptor register it. Ensure that we start getting
* events for the new file descriptor.
*
* This test exercises special logic in Looper.cpp for EPOLL_CTL_DEL handling EBADF.
*/
public void testPathologicalFileDescriptorReuseCallbacks1() throws Throwable {
// Prepare a special looper that we can catch exceptions from.
AssertableHandlerThread thread = new AssertableHandlerThread();
thread.start();
try {
final MessageQueue queue = thread.getLooper().getQueue();
final Handler handler = new Handler(thread.getLooper());
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
try (final FileInputStream reader = new AutoCloseInputStream(pipe[0]);
final FileOutputStream writer = new AutoCloseOutputStream(pipe[1])) {
// Register the callback.
final CountDownLatch awoke = new CountDownLatch(1);
queue.addOnFileDescriptorEventListener(reader.getFD(),
OnFileDescriptorEventListener.EVENT_ERROR,
new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
awoke.countDown();
// Close the reader before we return.
closeQuietly(reader);
// Return 0 to unregister the callback.
return 0;
}
});
// Close the writer to wake up the callback (due to hangup).
writer.close();
// Wait for the looper to catch up and run the callback.
assertTrue("awoke", awoke.await(TIMEOUT, TimeUnit.MILLISECONDS));
syncWait(handler);
}
// At this point, the reader and writer are both closed.
// Make a new pipe and ensure that things still work as expected.
final ParcelFileDescriptor[] pipe2 = ParcelFileDescriptor.createPipe();
try (final FileInputStream reader2 = new AutoCloseInputStream(pipe2[0]);
final FileOutputStream writer2 = new AutoCloseOutputStream(pipe2[1])) {
// Register the callback.
final CountDownLatch awoke = new CountDownLatch(1);
queue.addOnFileDescriptorEventListener(reader2.getFD(),
OnFileDescriptorEventListener.EVENT_INPUT,
new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
awoke.countDown();
// Return 0 to unregister the callback.
return 0;
}
});
// Close the writer to wake up the callback (due to hangup).
writer2.close();
// Wait for the looper to catch up and run the callback.
assertTrue("awoke", awoke.await(TIMEOUT, TimeUnit.MILLISECONDS));
syncWait(handler);
}
} finally {
thread.quitAndRethrow();
}
}
/**
* Since file descriptor numbers may be reused, there are some interesting
* edge cases around closing file descriptors within the callback and adding
* new ones with the same number.
*
* Register a file descriptor, close it from within the callback, reassign its
* number to a different pipe, then return. Later, register the same file descriptor
* again (now referring to a new pipe). Ensure that we start getting
* events for the new file descriptor.
*
* This test exercises special logic in Looper.cpp for EPOLL_CTL_DEL handling ENOENT.
*/
public void testPathologicalFileDescriptorReuseCallbacks2() throws Throwable {
// Prepare a special looper that we can catch exceptions from.
AssertableHandlerThread thread = new AssertableHandlerThread();
thread.start();
try {
final MessageQueue queue = thread.getLooper().getQueue();
final Handler handler = new Handler(thread.getLooper());
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
final ParcelFileDescriptor[] pipe2 = ParcelFileDescriptor.createPipe();
try {
final int oldReaderFd = pipe[0].getFd();
try (final FileInputStream reader = new AutoCloseInputStream(pipe[0]);
final FileOutputStream writer = new AutoCloseOutputStream(pipe[1])) {
// Register the callback.
final CountDownLatch awoke = new CountDownLatch(1);
queue.addOnFileDescriptorEventListener(reader.getFD(),
OnFileDescriptorEventListener.EVENT_ERROR,
new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
awoke.countDown();
// Close the reader before we return and hijack its fd.
hijackFd(pipe2, pipe);
// Return 0 to unregister the callback.
return 0;
}
});
// Close the writer to wake up the callback (due to hangup).
writer.close();
// Wait for the looper to catch up and run the callback.
assertTrue("awoke", awoke.await(TIMEOUT, TimeUnit.MILLISECONDS));
syncWait(handler);
}
// Now we have a new pipe with the same file descriptor, make sure we can
// register it successfully.
assertEquals(oldReaderFd, pipe2[0].getFd());
try (final FileInputStream reader2 = new AutoCloseInputStream(pipe2[0]);
final FileOutputStream writer2 = new AutoCloseOutputStream(pipe2[1])) {
// Register the callback.
final CountDownLatch awoke = new CountDownLatch(1);
queue.addOnFileDescriptorEventListener(reader2.getFD(),
OnFileDescriptorEventListener.EVENT_INPUT,
new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
awoke.countDown();
// Return 0 to unregister the callback.
return 0;
}
});
// Close the writer to wake up the callback (due to hangup).
writer2.close();
// Wait for the looper to catch up and run the callback.
assertTrue("awoke", awoke.await(TIMEOUT, TimeUnit.MILLISECONDS));
syncWait(handler);
}
} finally {
closeQuietly(pipe[0]);
closeQuietly(pipe[1]);
closeQuietly(pipe2[0]);
closeQuietly(pipe2[1]);
}
} finally {
thread.quitAndRethrow();
}
}
/**
* Since file descriptor numbers may be reused, there are some interesting
* edge cases around closing file descriptors within the callback and adding
* new ones with the same number.
*
* Register a file descriptor, close it from within the callback, reassign its
* number to a different pipe, register it, then return.
* Ensure that we start getting events for the new file descriptor.
*
* This test exercises special logic in Looper.cpp for EPOLL_CTL_MOD handling
* ENOENT and fallback to EPOLL_CTL_ADD as well as sequence number checks when removing
* the fd after the callback returns.
*/
public void testPathologicalFileDescriptorReuseCallbacks3() throws Throwable {
// Prepare a special looper that we can catch exceptions from.
AssertableHandlerThread thread = new AssertableHandlerThread();
thread.start();
try {
final MessageQueue queue = thread.getLooper().getQueue();
final Handler handler = new Handler(thread.getLooper());
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
final ParcelFileDescriptor[] pipe2 = ParcelFileDescriptor.createPipe();
try {
final CountDownLatch awoke2 = new CountDownLatch(1);
final int oldReaderFd = pipe[0].getFd();
try (final FileInputStream reader = new AutoCloseInputStream(pipe[0]);
final FileOutputStream writer = new AutoCloseOutputStream(pipe[1])) {
// Register the callback.
final CountDownLatch awoke = new CountDownLatch(1);
queue.addOnFileDescriptorEventListener(reader.getFD(),
OnFileDescriptorEventListener.EVENT_ERROR,
new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
awoke.countDown();
// Close the reader before we return and hijack its fd.
hijackFd(pipe2, pipe);
// Now we have a new pipe, make sure we can register it successfully.
queue.addOnFileDescriptorEventListener(pipe2[0].getFileDescriptor(),
OnFileDescriptorEventListener.EVENT_INPUT,
new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
awoke2.countDown();
// Return 0 to unregister the callback.
return 0;
}
});
// Return 0 to unregister the callback.
return 0;
}
});
// Close the writer to wake up the callback (due to hangup).
writer.close();
// Wait for the looper to catch up and run the callback.
assertTrue("awoke", awoke.await(TIMEOUT, TimeUnit.MILLISECONDS));
syncWait(handler);
}
// Close the second writer to wake up the second callback (due to hangup).
pipe2[1].close();
// Wait for the looper to catch up and run the callback.
assertTrue("awoke2", awoke2.await(TIMEOUT, TimeUnit.MILLISECONDS));
syncWait(handler);
// Close the second reader now that we're done with the test.
assertEquals(oldReaderFd, pipe2[0].getFd());
pipe2[0].close();
} finally {
closeQuietly(pipe[0]);
closeQuietly(pipe[1]);
closeQuietly(pipe2[0]);
closeQuietly(pipe2[1]);
}
} finally {
thread.quitAndRethrow();
}
}
/**
* Since file descriptor numbers may be reused, there are some interesting
* edge cases around closing file descriptors within the callback and adding
* new ones with the same number.
*
* Register a file descriptor, make a duplicate of it, close it from within the
* callback, then return. Look for signs that the Looper is spinning
* and never getting a chance to block.
*
* This test exercises special logic in Looper.cpp for rebuilding the epoll set
* in case it contains a file descriptor which has been closed and cannot be removed.
*/
public void testPathologicalFileDescriptorReuseCallbacks4() throws Throwable {
// Prepare a special looper that we can catch exceptions from.
ParcelFileDescriptor dup = null;
AssertableHandlerThread thread = new AssertableHandlerThread();
thread.start();
try {
try {
final MessageQueue queue = thread.getLooper().getQueue();
final Handler handler = new Handler(thread.getLooper());
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
dup = pipe[0].dup();
try (final FileInputStream reader = new AutoCloseInputStream(pipe[0]);
final FileOutputStream writer = new AutoCloseOutputStream(pipe[1])) {
// Register the callback.
final CountDownLatch awoke = new CountDownLatch(1);
queue.addOnFileDescriptorEventListener(reader.getFD(),
OnFileDescriptorEventListener.EVENT_ERROR, new OnFileDescriptorEventListener() {
@Override
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
awoke.countDown();
// Close the file descriptor before we return.
try {
reader.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
// Return 0 to unregister the callback.
return 0;
}
});
// Close the writer to wake up the callback (due to hangup).
writer.close();
// Wait for the looper to catch up and run the callback.
assertTrue("awoke", awoke.await(TIMEOUT, TimeUnit.MILLISECONDS));
syncWait(handler);
}
// Wait a little bit before we stop the thread.
Thread.sleep(2000);
} finally {
// Check for how long the thread was running.
// If the Looper behaved correctly, then it should have blocked for most of
// the duration of the test (including that sleep above) since not much else
// was happening. If we failed to actually rebuild the epoll set then the
// Looper may have been spinning continuously due to an FD that was never
// properly removed from the epoll set so the thread runtime will be very high.
long runtime = thread.quitAndRethrow();
assertFalse("Looper thread spent most of its time spinning instead of blocked.",
runtime > 1000);
}
} finally {
// Close the duplicate now that we are done with it.
if (dup != null) {
dup.close();
}
}
}
public void testSyncBarriers() throws Exception {
OrderTestHelper tester = new OrderTestHelper() {
private int mBarrierToken1;
private int mBarrierToken2;
@Override
public void init() {
super.init();
mLastMessage = 10;
mHandler.sendEmptyMessage(0);
mBarrierToken1 = Looper.myQueue().postSyncBarrier();
mHandler.sendEmptyMessage(5);
sendAsyncMessage(1);
sendAsyncMessage(2);
sendAsyncMessage(3);
mHandler.sendEmptyMessage(6);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 3) {
mHandler.sendEmptyMessage(7);
mBarrierToken2 = Looper.myQueue().postSyncBarrier();
sendAsyncMessage(4);
sendAsyncMessage(8);
} else if (msg.what == 4) {
Looper.myQueue().removeSyncBarrier(mBarrierToken1);
sendAsyncMessage(9);
mHandler.sendEmptyMessage(10);
} else if (msg.what == 8) {
Looper.myQueue().removeSyncBarrier(mBarrierToken2);
}
}
private void sendAsyncMessage(int what) {
Message msg = mHandler.obtainMessage(what);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
};
tester.doTest(1000, 50);
}
public void testReleaseSyncBarrierThrowsIfTokenNotValid() throws Exception {
MessageQueue queue = Looper.getMainLooper().getQueue();
// Invalid token
try {
queue.removeSyncBarrier(-1);
fail("Should have thrown IllegalStateException");
} catch (IllegalStateException ex) {
// expected
}
// Token already removed.
int barrierToken = queue.postSyncBarrier();
queue.removeSyncBarrier(barrierToken);
try {
queue.removeSyncBarrier(barrierToken);
fail("Should have thrown IllegalStateException");
} catch (IllegalStateException ex) {
// expected
}
}
private void syncWait(Handler handler) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
handler.post(new Runnable() {
@Override
public void run() {
latch.countDown();
}
});
assertTrue("Handler got stuck.", latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
}
private static void closeQuietly(AutoCloseable c) {
if (c != null) {
try {
c.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ex) {
}
}
}
private static void hijackFd(ParcelFileDescriptor[] newPipe, ParcelFileDescriptor[] oldPipe) {
// Detach the old pipe's first fd and get its number.
int fd = oldPipe[0].detachFd();
// Assign the new pipe's first fd to the same number as the old pipe's first fd.
// This causes the old pipe's first fd to be closed and reassigned.
try {
Os.dup2(newPipe[0].getFileDescriptor(), fd);
} catch (ErrnoException ex) {
throw new RuntimeException(ex);
}
// Fix up the new pipe's first fd object.
closeQuietly(newPipe[0]);
newPipe[0] = ParcelFileDescriptor.adoptFd(fd);
}
/**
* Helper class used to test sending message to message queue.
*/
private class OrderTestHelper {
Handler mHandler;
int mLastMessage;
int mCount;
private boolean mSuccess;
private RuntimeException mFailure;
private boolean mDone;
private Looper mLooper;
public void init() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
OrderTestHelper.this.handleMessage(msg);
}
};
}
public void handleMessage(Message msg) {
if (mCount <= mLastMessage) {
if (msg.what != mCount) {
failure(new RuntimeException("Expected message #" + mCount + ", received #"
+ msg.what));
} else if (mCount == mLastMessage) {
success();
}
mCount++;
} else {
failure(new RuntimeException("Message received after done, #" + msg.what));
}
}
public void doTest(long timeout, long interval) throws InterruptedException {
(new LooperThread()).start();
synchronized (this) {
long now = System.currentTimeMillis();
long endTime = now + timeout;
while (!mDone && now < endTime) {
wait(interval);
now = System.currentTimeMillis();
}
}
mLooper.quit();
if (!mDone) {
throw new RuntimeException("test timed out");
}
if (!mSuccess) {
throw mFailure;
}
}
class LooperThread extends HandlerThread {
public LooperThread() {
super("MessengerLooperThread");
}
@Override
public void onLooperPrepared() {
init();
mLooper = getLooper();
}
@Override
public void run() {
super.run();
synchronized (OrderTestHelper.this) {
mDone = true;
if (!mSuccess && mFailure == null) {
mFailure = new RuntimeException("no failure exception set");
}
OrderTestHelper.this.notifyAll();
}
}
}
public void success() {
synchronized (this) {
mSuccess = true;
quit();
}
}
public void failure(RuntimeException failure) {
synchronized (this) {
mSuccess = false;
mFailure = failure;
quit();
}
}
private void quit() {
synchronized (this) {
mDone = true;
notifyAll();
}
}
}
/**
* A HandlerThread that propagates exceptions out of the event loop
* instead of crashing the process.
*/
private static class AssertableHandlerThread extends HandlerThread {
private Throwable mThrowable;
private long mRuntime;
public AssertableHandlerThread() {
super("AssertableHandlerThread");
}
@Override
public void run() {
final long startTime = SystemClock.currentThreadTimeMillis();
try {
super.run();
} catch (Throwable t) {
mThrowable = t;
} finally {
mRuntime = SystemClock.currentThreadTimeMillis() - startTime;
}
}
public long quitAndRethrow() throws Throwable {
quitSafely();
join(TIMEOUT);
if (mThrowable != null) {
throw mThrowable;
}
return mRuntime;
}
}
}