Loading tests/utils/testutils/java/android/os/test/TestLooper.java +148 −22 Original line number Original line Diff line number Diff line Loading @@ -18,18 +18,24 @@ package android.os.test; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import android.os.Build; import android.os.Handler; import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerExecutor; import android.os.Looper; import android.os.Looper; import android.os.Message; import android.os.Message; import android.os.MessageQueue; import android.os.MessageQueue; import android.os.SystemClock; import android.os.SystemClock; import android.os.TestLooperManager; import android.util.Log; import android.util.Log; import androidx.test.InstrumentationRegistry; import java.lang.reflect.Constructor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.Executor; /** /** Loading @@ -44,7 +50,9 @@ import java.util.concurrent.Executor; * The Robolectric class also allows advancing time. * The Robolectric class also allows advancing time. */ */ public class TestLooper { public class TestLooper { protected final Looper mLooper; private final Looper mLooper; private final TestLooperManager mTestLooperManager; private final Clock mClock; private static final Constructor<Looper> LOOPER_CONSTRUCTOR; private static final Constructor<Looper> LOOPER_CONSTRUCTOR; private static final Field THREAD_LOCAL_LOOPER_FIELD; private static final Field THREAD_LOCAL_LOOPER_FIELD; Loading @@ -54,16 +62,28 @@ public class TestLooper { private static final Method MESSAGE_MARK_IN_USE_METHOD; private static final Method MESSAGE_MARK_IN_USE_METHOD; private static final String TAG = "TestLooper"; private static final String TAG = "TestLooper"; private final Clock mClock; private AutoDispatchThread mAutoDispatchThread; private AutoDispatchThread mAutoDispatchThread; /** * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. */ private static boolean isAtLeastBaklava() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; } static { static { try { try { LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); LOOPER_CONSTRUCTOR.setAccessible(true); LOOPER_CONSTRUCTOR.setAccessible(true); THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); if (isAtLeastBaklava()) { MESSAGE_QUEUE_MESSAGES_FIELD = null; MESSAGE_NEXT_FIELD = null; MESSAGE_WHEN_FIELD = null; MESSAGE_MARK_IN_USE_METHOD = null; } else { MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); Loading @@ -72,6 +92,7 @@ public class TestLooper { MESSAGE_WHEN_FIELD.setAccessible(true); MESSAGE_WHEN_FIELD.setAccessible(true); MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); } } catch (NoSuchFieldException | NoSuchMethodException e) { } catch (NoSuchFieldException | NoSuchMethodException e) { throw new RuntimeException("Failed to initialize TestLooper", e); throw new RuntimeException("Failed to initialize TestLooper", e); } } Loading Loading @@ -106,6 +127,13 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); throw new RuntimeException("Reflection error constructing or accessing looper", e); } } if (isAtLeastBaklava()) { mTestLooperManager = InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); } else { mTestLooperManager = null; } mClock = clock; mClock = clock; } } Loading @@ -117,7 +145,7 @@ public class TestLooper { return new HandlerExecutor(new Handler(getLooper())); return new HandlerExecutor(new Handler(getLooper())); } } private Message getMessageLinkedList() { private Message getMessageLinkedListLegacy() { try { try { MessageQueue queue = mLooper.getQueue(); MessageQueue queue = mLooper.getQueue(); return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); Loading @@ -128,8 +156,61 @@ public class TestLooper { } } public void moveTimeForward(long milliSeconds) { public void moveTimeForward(long milliSeconds) { if (isAtLeastBaklava()) { moveTimeForwardBaklava(milliSeconds); } else { moveTimeForwardLegacy(milliSeconds); } } private void moveTimeForwardBaklava(long milliSeconds) { // Drain all Messages from the queue. Queue<Message> messages = new ArrayDeque<>(); while (true) { Message message = mTestLooperManager.poll(); if (message == null) { break; } // Adjust the Message's delivery time. long newWhen = message.when - milliSeconds; if (newWhen < 0) { newWhen = 0; } message.when = newWhen; messages.add(message); } // Repost all Messages back to the queuewith a new time. while (true) { Message message = messages.poll(); if (message == null) { break; } Runnable callback = message.getCallback(); Handler handler = message.getTarget(); long when = message.getWhen(); // The Message cannot be re-enqueued because it is marked in use. // Make a copy of the Message and recycle the original. // This resets {@link Message#isInUse()} but retains all other content. { Message newMessage = Message.obtain(); newMessage.copyFrom(message); newMessage.setCallback(callback); mTestLooperManager.recycle(message); message = newMessage; } // Send the Message back to its Handler to be re-enqueued. handler.sendMessageAtTime(message, when); } } private void moveTimeForwardLegacy(long milliSeconds) { try { try { Message msg = getMessageLinkedList(); Message msg = getMessageLinkedListLegacy(); while (msg != null) { while (msg != null) { long updatedWhen = msg.getWhen() - milliSeconds; long updatedWhen = msg.getWhen() - milliSeconds; if (updatedWhen < 0) { if (updatedWhen < 0) { Loading @@ -147,12 +228,12 @@ public class TestLooper { return mClock.uptimeMillis(); return mClock.uptimeMillis(); } } private Message messageQueueNext() { private Message messageQueueNextLegacy() { try { try { long now = currentTime(); long now = currentTime(); Message prevMsg = null; Message prevMsg = null; Message msg = getMessageLinkedList(); Message msg = getMessageLinkedListLegacy(); if (msg != null && msg.getTarget() == null) { if (msg != null && msg.getTarget() == null) { // Stalled by a barrier. Find the next asynchronous message in // Stalled by a barrier. Find the next asynchronous message in // the queue. // the queue. Loading Loading @@ -185,18 +266,46 @@ public class TestLooper { /** /** * @return true if there are pending messages in the message queue * @return true if there are pending messages in the message queue */ */ public synchronized boolean isIdle() { public boolean isIdle() { Message messageList = getMessageLinkedList(); if (isAtLeastBaklava()) { return isIdleBaklava(); } else { return isIdleLegacy(); } } private boolean isIdleBaklava() { Long when = mTestLooperManager.peekWhen(); return when != null && currentTime() >= when; } private synchronized boolean isIdleLegacy() { Message messageList = getMessageLinkedListLegacy(); return messageList != null && currentTime() >= messageList.getWhen(); return messageList != null && currentTime() >= messageList.getWhen(); } } /** /** * @return the next message in the Looper's message queue or null if there is none * @return the next message in the Looper's message queue or null if there is none */ */ public synchronized Message nextMessage() { public Message nextMessage() { if (isAtLeastBaklava()) { return nextMessageBaklava(); } else { return nextMessageLegacy(); } } private Message nextMessageBaklava() { if (isIdle()) { return mTestLooperManager.poll(); } else { return null; } } private synchronized Message nextMessageLegacy() { if (isIdle()) { if (isIdle()) { return messageQueueNext(); return messageQueueNextLegacy(); } else { } else { return null; return null; } } Loading @@ -206,9 +315,26 @@ public class TestLooper { * Dispatch the next message in the queue * Dispatch the next message in the queue * Asserts that there is a message in the queue * Asserts that there is a message in the queue */ */ public synchronized void dispatchNext() { public void dispatchNext() { if (isAtLeastBaklava()) { dispatchNextBaklava(); } else { dispatchNextLegacy(); } } private void dispatchNextBaklava() { assertTrue(isIdle()); Message msg = mTestLooperManager.poll(); if (msg == null) { return; } msg.getTarget().dispatchMessage(msg); } private synchronized void dispatchNextLegacy() { assertTrue(isIdle()); assertTrue(isIdle()); Message msg = messageQueueNext(); Message msg = messageQueueNextLegacy(); if (msg == null) { if (msg == null) { return; return; } } Loading Loading
tests/utils/testutils/java/android/os/test/TestLooper.java +148 −22 Original line number Original line Diff line number Diff line Loading @@ -18,18 +18,24 @@ package android.os.test; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import android.os.Build; import android.os.Handler; import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerExecutor; import android.os.Looper; import android.os.Looper; import android.os.Message; import android.os.Message; import android.os.MessageQueue; import android.os.MessageQueue; import android.os.SystemClock; import android.os.SystemClock; import android.os.TestLooperManager; import android.util.Log; import android.util.Log; import androidx.test.InstrumentationRegistry; import java.lang.reflect.Constructor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.Executor; /** /** Loading @@ -44,7 +50,9 @@ import java.util.concurrent.Executor; * The Robolectric class also allows advancing time. * The Robolectric class also allows advancing time. */ */ public class TestLooper { public class TestLooper { protected final Looper mLooper; private final Looper mLooper; private final TestLooperManager mTestLooperManager; private final Clock mClock; private static final Constructor<Looper> LOOPER_CONSTRUCTOR; private static final Constructor<Looper> LOOPER_CONSTRUCTOR; private static final Field THREAD_LOCAL_LOOPER_FIELD; private static final Field THREAD_LOCAL_LOOPER_FIELD; Loading @@ -54,16 +62,28 @@ public class TestLooper { private static final Method MESSAGE_MARK_IN_USE_METHOD; private static final Method MESSAGE_MARK_IN_USE_METHOD; private static final String TAG = "TestLooper"; private static final String TAG = "TestLooper"; private final Clock mClock; private AutoDispatchThread mAutoDispatchThread; private AutoDispatchThread mAutoDispatchThread; /** * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. */ private static boolean isAtLeastBaklava() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; } static { static { try { try { LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); LOOPER_CONSTRUCTOR.setAccessible(true); LOOPER_CONSTRUCTOR.setAccessible(true); THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); if (isAtLeastBaklava()) { MESSAGE_QUEUE_MESSAGES_FIELD = null; MESSAGE_NEXT_FIELD = null; MESSAGE_WHEN_FIELD = null; MESSAGE_MARK_IN_USE_METHOD = null; } else { MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); Loading @@ -72,6 +92,7 @@ public class TestLooper { MESSAGE_WHEN_FIELD.setAccessible(true); MESSAGE_WHEN_FIELD.setAccessible(true); MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); } } catch (NoSuchFieldException | NoSuchMethodException e) { } catch (NoSuchFieldException | NoSuchMethodException e) { throw new RuntimeException("Failed to initialize TestLooper", e); throw new RuntimeException("Failed to initialize TestLooper", e); } } Loading Loading @@ -106,6 +127,13 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); throw new RuntimeException("Reflection error constructing or accessing looper", e); } } if (isAtLeastBaklava()) { mTestLooperManager = InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); } else { mTestLooperManager = null; } mClock = clock; mClock = clock; } } Loading @@ -117,7 +145,7 @@ public class TestLooper { return new HandlerExecutor(new Handler(getLooper())); return new HandlerExecutor(new Handler(getLooper())); } } private Message getMessageLinkedList() { private Message getMessageLinkedListLegacy() { try { try { MessageQueue queue = mLooper.getQueue(); MessageQueue queue = mLooper.getQueue(); return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); Loading @@ -128,8 +156,61 @@ public class TestLooper { } } public void moveTimeForward(long milliSeconds) { public void moveTimeForward(long milliSeconds) { if (isAtLeastBaklava()) { moveTimeForwardBaklava(milliSeconds); } else { moveTimeForwardLegacy(milliSeconds); } } private void moveTimeForwardBaklava(long milliSeconds) { // Drain all Messages from the queue. Queue<Message> messages = new ArrayDeque<>(); while (true) { Message message = mTestLooperManager.poll(); if (message == null) { break; } // Adjust the Message's delivery time. long newWhen = message.when - milliSeconds; if (newWhen < 0) { newWhen = 0; } message.when = newWhen; messages.add(message); } // Repost all Messages back to the queuewith a new time. while (true) { Message message = messages.poll(); if (message == null) { break; } Runnable callback = message.getCallback(); Handler handler = message.getTarget(); long when = message.getWhen(); // The Message cannot be re-enqueued because it is marked in use. // Make a copy of the Message and recycle the original. // This resets {@link Message#isInUse()} but retains all other content. { Message newMessage = Message.obtain(); newMessage.copyFrom(message); newMessage.setCallback(callback); mTestLooperManager.recycle(message); message = newMessage; } // Send the Message back to its Handler to be re-enqueued. handler.sendMessageAtTime(message, when); } } private void moveTimeForwardLegacy(long milliSeconds) { try { try { Message msg = getMessageLinkedList(); Message msg = getMessageLinkedListLegacy(); while (msg != null) { while (msg != null) { long updatedWhen = msg.getWhen() - milliSeconds; long updatedWhen = msg.getWhen() - milliSeconds; if (updatedWhen < 0) { if (updatedWhen < 0) { Loading @@ -147,12 +228,12 @@ public class TestLooper { return mClock.uptimeMillis(); return mClock.uptimeMillis(); } } private Message messageQueueNext() { private Message messageQueueNextLegacy() { try { try { long now = currentTime(); long now = currentTime(); Message prevMsg = null; Message prevMsg = null; Message msg = getMessageLinkedList(); Message msg = getMessageLinkedListLegacy(); if (msg != null && msg.getTarget() == null) { if (msg != null && msg.getTarget() == null) { // Stalled by a barrier. Find the next asynchronous message in // Stalled by a barrier. Find the next asynchronous message in // the queue. // the queue. Loading Loading @@ -185,18 +266,46 @@ public class TestLooper { /** /** * @return true if there are pending messages in the message queue * @return true if there are pending messages in the message queue */ */ public synchronized boolean isIdle() { public boolean isIdle() { Message messageList = getMessageLinkedList(); if (isAtLeastBaklava()) { return isIdleBaklava(); } else { return isIdleLegacy(); } } private boolean isIdleBaklava() { Long when = mTestLooperManager.peekWhen(); return when != null && currentTime() >= when; } private synchronized boolean isIdleLegacy() { Message messageList = getMessageLinkedListLegacy(); return messageList != null && currentTime() >= messageList.getWhen(); return messageList != null && currentTime() >= messageList.getWhen(); } } /** /** * @return the next message in the Looper's message queue or null if there is none * @return the next message in the Looper's message queue or null if there is none */ */ public synchronized Message nextMessage() { public Message nextMessage() { if (isAtLeastBaklava()) { return nextMessageBaklava(); } else { return nextMessageLegacy(); } } private Message nextMessageBaklava() { if (isIdle()) { return mTestLooperManager.poll(); } else { return null; } } private synchronized Message nextMessageLegacy() { if (isIdle()) { if (isIdle()) { return messageQueueNext(); return messageQueueNextLegacy(); } else { } else { return null; return null; } } Loading @@ -206,9 +315,26 @@ public class TestLooper { * Dispatch the next message in the queue * Dispatch the next message in the queue * Asserts that there is a message in the queue * Asserts that there is a message in the queue */ */ public synchronized void dispatchNext() { public void dispatchNext() { if (isAtLeastBaklava()) { dispatchNextBaklava(); } else { dispatchNextLegacy(); } } private void dispatchNextBaklava() { assertTrue(isIdle()); Message msg = mTestLooperManager.poll(); if (msg == null) { return; } msg.getTarget().dispatchMessage(msg); } private synchronized void dispatchNextLegacy() { assertTrue(isIdle()); assertTrue(isIdle()); Message msg = messageQueueNext(); Message msg = messageQueueNextLegacy(); if (msg == null) { if (msg == null) { return; return; } } Loading