Loading tests/utils/testutils/java/android/os/test/TestLooper.java +75 −53 Original line number Original line Diff line number Diff line Loading @@ -24,16 +24,12 @@ 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.util.ArrayDeque; import java.lang.reflect.Method; import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.Executor; /** /** Loading @@ -48,14 +44,17 @@ 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 { private final Looper mLooper; protected 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; private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; private static final Field MESSAGE_NEXT_FIELD; private static final Field MESSAGE_WHEN_FIELD; 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; Loading @@ -65,6 +64,14 @@ public class TestLooper { 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); MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); MESSAGE_NEXT_FIELD.setAccessible(true); MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); MESSAGE_WHEN_FIELD.setAccessible(true); MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); 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 @@ -99,8 +106,6 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); throw new RuntimeException("Reflection error constructing or accessing looper", e); } } mTestLooperManager = InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); mClock = clock; mClock = clock; } } Loading @@ -112,61 +117,78 @@ public class TestLooper { return new HandlerExecutor(new Handler(getLooper())); return new HandlerExecutor(new Handler(getLooper())); } } public void moveTimeForward(long milliSeconds) { private Message getMessageLinkedList() { // Drain all Messages from the queue. try { Queue<Message> messages = new ArrayDeque<>(); MessageQueue queue = mLooper.getQueue(); while (true) { return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); Message message = mTestLooperManager.poll(); } catch (IllegalAccessException e) { if (message == null) { throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages", break; e); } } } // Adjust the Message's delivery time. public void moveTimeForward(long milliSeconds) { long newWhen = message.when - milliSeconds; try { if (newWhen < 0) { Message msg = getMessageLinkedList(); newWhen = 0; while (msg != null) { long updatedWhen = msg.getWhen() - milliSeconds; if (updatedWhen < 0) { updatedWhen = 0; } } message.when = newWhen; MESSAGE_WHEN_FIELD.set(msg, updatedWhen); messages.add(message); msg = (Message) MESSAGE_NEXT_FIELD.get(msg); } } catch (IllegalAccessException e) { throw new RuntimeException("Access failed in TestLooper: set - Message.when", e); } } // Repost all Messages back to the queuewith a new time. while (true) { Message message = messages.poll(); if (message == null) { break; } } Runnable callback = message.getCallback(); private long currentTime() { Handler handler = message.getTarget(); return mClock.uptimeMillis(); 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. private Message messageQueueNext() { handler.sendMessageAtTime(message, when); try { long now = currentTime(); Message prevMsg = null; Message msg = getMessageLinkedList(); if (msg != null && msg.getTarget() == null) { // Stalled by a barrier. Find the next asynchronous message in // the queue. do { prevMsg = msg; msg = (Message) MESSAGE_NEXT_FIELD.get(msg); } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now >= msg.getWhen()) { // Got a message. if (prevMsg != null) { MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg)); } else { MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(), MESSAGE_NEXT_FIELD.get(msg)); } MESSAGE_NEXT_FIELD.set(msg, null); MESSAGE_MARK_IN_USE_METHOD.invoke(msg); return msg; } } } } } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Access failed in TestLooper", e); } private long currentTime() { return null; return mClock.uptimeMillis(); } } /** /** * @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 synchronized boolean isIdle() { Long when = mTestLooperManager.peekWhen(); Message messageList = getMessageLinkedList(); return when != null && currentTime() >= when; return messageList != null && currentTime() >= messageList.getWhen(); } } /** /** Loading @@ -174,7 +196,7 @@ public class TestLooper { */ */ public synchronized Message nextMessage() { public synchronized Message nextMessage() { if (isIdle()) { if (isIdle()) { return mTestLooperManager.poll(); return messageQueueNext(); } else { } else { return null; return null; } } Loading @@ -186,7 +208,7 @@ public class TestLooper { */ */ public synchronized void dispatchNext() { public synchronized void dispatchNext() { assertTrue(isIdle()); assertTrue(isIdle()); Message msg = mTestLooperManager.poll(); Message msg = messageQueueNext(); if (msg == null) { if (msg == null) { return; return; } } Loading Loading
tests/utils/testutils/java/android/os/test/TestLooper.java +75 −53 Original line number Original line Diff line number Diff line Loading @@ -24,16 +24,12 @@ 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.util.ArrayDeque; import java.lang.reflect.Method; import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.Executor; /** /** Loading @@ -48,14 +44,17 @@ 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 { private final Looper mLooper; protected 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; private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; private static final Field MESSAGE_NEXT_FIELD; private static final Field MESSAGE_WHEN_FIELD; 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; Loading @@ -65,6 +64,14 @@ public class TestLooper { 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); MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); MESSAGE_NEXT_FIELD.setAccessible(true); MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); MESSAGE_WHEN_FIELD.setAccessible(true); MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); 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 @@ -99,8 +106,6 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); throw new RuntimeException("Reflection error constructing or accessing looper", e); } } mTestLooperManager = InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); mClock = clock; mClock = clock; } } Loading @@ -112,61 +117,78 @@ public class TestLooper { return new HandlerExecutor(new Handler(getLooper())); return new HandlerExecutor(new Handler(getLooper())); } } public void moveTimeForward(long milliSeconds) { private Message getMessageLinkedList() { // Drain all Messages from the queue. try { Queue<Message> messages = new ArrayDeque<>(); MessageQueue queue = mLooper.getQueue(); while (true) { return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); Message message = mTestLooperManager.poll(); } catch (IllegalAccessException e) { if (message == null) { throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages", break; e); } } } // Adjust the Message's delivery time. public void moveTimeForward(long milliSeconds) { long newWhen = message.when - milliSeconds; try { if (newWhen < 0) { Message msg = getMessageLinkedList(); newWhen = 0; while (msg != null) { long updatedWhen = msg.getWhen() - milliSeconds; if (updatedWhen < 0) { updatedWhen = 0; } } message.when = newWhen; MESSAGE_WHEN_FIELD.set(msg, updatedWhen); messages.add(message); msg = (Message) MESSAGE_NEXT_FIELD.get(msg); } } catch (IllegalAccessException e) { throw new RuntimeException("Access failed in TestLooper: set - Message.when", e); } } // Repost all Messages back to the queuewith a new time. while (true) { Message message = messages.poll(); if (message == null) { break; } } Runnable callback = message.getCallback(); private long currentTime() { Handler handler = message.getTarget(); return mClock.uptimeMillis(); 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. private Message messageQueueNext() { handler.sendMessageAtTime(message, when); try { long now = currentTime(); Message prevMsg = null; Message msg = getMessageLinkedList(); if (msg != null && msg.getTarget() == null) { // Stalled by a barrier. Find the next asynchronous message in // the queue. do { prevMsg = msg; msg = (Message) MESSAGE_NEXT_FIELD.get(msg); } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now >= msg.getWhen()) { // Got a message. if (prevMsg != null) { MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg)); } else { MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(), MESSAGE_NEXT_FIELD.get(msg)); } MESSAGE_NEXT_FIELD.set(msg, null); MESSAGE_MARK_IN_USE_METHOD.invoke(msg); return msg; } } } } } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Access failed in TestLooper", e); } private long currentTime() { return null; return mClock.uptimeMillis(); } } /** /** * @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 synchronized boolean isIdle() { Long when = mTestLooperManager.peekWhen(); Message messageList = getMessageLinkedList(); return when != null && currentTime() >= when; return messageList != null && currentTime() >= messageList.getWhen(); } } /** /** Loading @@ -174,7 +196,7 @@ public class TestLooper { */ */ public synchronized Message nextMessage() { public synchronized Message nextMessage() { if (isIdle()) { if (isIdle()) { return mTestLooperManager.poll(); return messageQueueNext(); } else { } else { return null; return null; } } Loading @@ -186,7 +208,7 @@ public class TestLooper { */ */ public synchronized void dispatchNext() { public synchronized void dispatchNext() { assertTrue(isIdle()); assertTrue(isIdle()); Message msg = mTestLooperManager.poll(); Message msg = messageQueueNext(); if (msg == null) { if (msg == null) { return; return; } } Loading