1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.M; 4 import static com.google.common.truth.Truth.assertThat; 5 import static org.junit.Assert.fail; 6 import static org.robolectric.Shadows.shadowOf; 7 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 8 import static org.robolectric.util.ReflectionHelpers.callConstructor; 9 import static org.robolectric.util.ReflectionHelpers.callInstanceMethod; 10 import static org.robolectric.util.ReflectionHelpers.getField; 11 import static org.robolectric.util.ReflectionHelpers.setField; 12 13 import android.os.Build; 14 import android.os.Handler; 15 import android.os.Looper; 16 import android.os.Message; 17 import android.os.MessageQueue; 18 import android.os.SystemClock; 19 import androidx.test.ext.junit.runners.AndroidJUnit4; 20 import java.util.ArrayList; 21 import java.util.List; 22 import org.junit.Before; 23 import org.junit.Ignore; 24 import org.junit.Test; 25 import org.junit.runner.RunWith; 26 import org.robolectric.RuntimeEnvironment; 27 import org.robolectric.util.ReflectionHelpers; 28 import org.robolectric.util.Scheduler; 29 30 @RunWith(AndroidJUnit4.class) 31 public class ShadowMessageQueueTest { 32 private Looper looper; 33 private MessageQueue queue; 34 private ShadowMessageQueue shadowQueue; 35 private Message testMessage; 36 private TestHandler handler; 37 private Scheduler scheduler; 38 private String quitField; 39 40 private static class TestHandler extends Handler { 41 public List<Message> handled = new ArrayList<>(); 42 TestHandler(Looper looper)43 public TestHandler(Looper looper) { 44 super(looper); 45 } 46 47 @Override handleMessage(Message msg)48 public void handleMessage(Message msg) { 49 handled.add(msg); 50 } 51 } 52 newLooper()53 private static Looper newLooper() { 54 return newLooper(true); 55 } 56 newLooper(boolean canQuit)57 private static Looper newLooper(boolean canQuit) { 58 return callConstructor(Looper.class, from(boolean.class, canQuit)); 59 } 60 61 @Before setUp()62 public void setUp() throws Exception { 63 // Queues and loopers are closely linked; can't easily test one without the other. 64 looper = newLooper(); 65 handler = new TestHandler(looper); 66 queue = looper.getQueue(); 67 shadowQueue = shadowOf(queue); 68 scheduler = shadowQueue.getScheduler(); 69 scheduler.pause(); 70 testMessage = handler.obtainMessage(); 71 quitField = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? "mQuitting" : "mQuiting"; 72 } 73 74 @Test test_setGetHead()75 public void test_setGetHead() { 76 shadowQueue.setHead(testMessage); 77 assertThat(shadowQueue.getHead()).named("getHead()").isSameAs(testMessage); 78 } 79 enqueueMessage(Message msg, long when)80 private boolean enqueueMessage(Message msg, long when) { 81 return callInstanceMethod(queue, "enqueueMessage", 82 from(Message.class, msg), 83 from(long.class, when) 84 ); 85 } 86 removeMessages(Handler handler, int what, Object token)87 private void removeMessages(Handler handler, int what, Object token) { 88 callInstanceMethod(queue, "removeMessages", 89 from(Handler.class, handler), 90 from(int.class, what), 91 from(Object.class, token) 92 ); 93 } 94 95 @Test enqueueMessage_setsHead()96 public void enqueueMessage_setsHead() { 97 enqueueMessage(testMessage, 100); 98 assertThat(shadowQueue.getHead()).named("head").isSameAs(testMessage); 99 } 100 101 @Test enqueueMessage_returnsTrue()102 public void enqueueMessage_returnsTrue() { 103 assertThat(enqueueMessage(testMessage, 100)).named("retval").isTrue(); 104 } 105 106 @Test enqueueMessage_setsWhen()107 public void enqueueMessage_setsWhen() { 108 enqueueMessage(testMessage, 123); 109 assertThat(testMessage.getWhen()).named("when").isEqualTo(123); 110 } 111 112 @Test enqueueMessage_returnsFalse_whenQuitting()113 public void enqueueMessage_returnsFalse_whenQuitting() { 114 setField(queue, quitField, true); 115 assertThat(enqueueMessage(testMessage, 1)).named("enqueueMessage()").isFalse(); 116 } 117 118 @Test enqueueMessage_doesntSchedule_whenQuitting()119 public void enqueueMessage_doesntSchedule_whenQuitting() { 120 setField(queue, quitField, true); 121 enqueueMessage(testMessage, 1); 122 assertThat(scheduler.size()).named("scheduler_size").isEqualTo(0); 123 } 124 125 @Test enqueuedMessage_isSentToHandler()126 public void enqueuedMessage_isSentToHandler() { 127 enqueueMessage(testMessage, 200); 128 scheduler.advanceTo(199); 129 assertThat(handler.handled).named("handled:before").isEmpty(); 130 scheduler.advanceTo(200); 131 assertThat(handler.handled).named("handled:after").containsExactly(testMessage); 132 } 133 134 @Test removedMessage_isNotSentToHandler()135 public void removedMessage_isNotSentToHandler() { 136 enqueueMessage(testMessage, 200); 137 assertThat(scheduler.size()).named("scheduler size:before").isEqualTo(1); 138 removeMessages(handler, testMessage.what, null); 139 scheduler.advanceToLastPostedRunnable(); 140 assertThat(scheduler.size()).named("scheduler size:after").isEqualTo(0); 141 assertThat(handler.handled).named("handled").isEmpty(); 142 } 143 144 @Test enqueueMessage_withZeroWhen_postsAtFront()145 public void enqueueMessage_withZeroWhen_postsAtFront() { 146 enqueueMessage(testMessage, 0); 147 Message m2 = handler.obtainMessage(2); 148 enqueueMessage(m2, 0); 149 scheduler.advanceToLastPostedRunnable(); 150 assertThat(handler.handled).named("handled").containsExactly(m2, testMessage); 151 } 152 153 @Test dispatchedMessage_isMarkedInUse_andRecycled()154 public void dispatchedMessage_isMarkedInUse_andRecycled() { 155 Handler handler = new Handler(looper) { 156 @Override 157 public void handleMessage(Message msg) { 158 boolean inUse = callInstanceMethod(msg, "isInUse"); 159 assertThat(inUse).named(msg.what + ":inUse").isTrue(); 160 Message next = getField(msg, "next"); 161 assertThat(next).named(msg.what + ":next").isNull(); 162 } 163 }; 164 Message msg = handler.obtainMessage(1); 165 enqueueMessage(msg, 200); 166 Message msg2 = handler.obtainMessage(2); 167 enqueueMessage(msg2, 205); 168 scheduler.advanceToNextPostedRunnable(); 169 170 // Check that it's been properly recycled. 171 assertThat(msg.what).named("msg.what").isEqualTo(0); 172 173 scheduler.advanceToNextPostedRunnable(); 174 175 assertThat(msg2.what).named("msg2.what").isEqualTo(0); 176 } 177 178 @Test reset_shouldClearMessageQueue()179 public void reset_shouldClearMessageQueue() { 180 Message msg = handler.obtainMessage(1234); 181 Message msg2 = handler.obtainMessage(5678); 182 handler.sendMessage(msg); 183 handler.sendMessage(msg2); 184 assertThat(handler.hasMessages(1234)).named("before-1234").isTrue(); 185 assertThat(handler.hasMessages(5678)).named("before-5678").isTrue(); 186 shadowQueue.reset(); 187 assertThat(handler.hasMessages(1234)).named("after-1234").isFalse(); 188 assertThat(handler.hasMessages(5678)).named("after-5678").isFalse(); 189 } 190 191 @Test postAndRemoveSyncBarrierToken()192 public void postAndRemoveSyncBarrierToken() { 193 int token = postSyncBarrier(queue); 194 removeSyncBarrier(queue, token); 195 } 196 197 @Test 198 // TODO(b/74402484): enable once workaround is removed 199 @Ignore removeInvalidSyncBarrierToken()200 public void removeInvalidSyncBarrierToken() { 201 try { 202 removeSyncBarrier(queue, 99); 203 fail("Expected exception when sync barrier not present on MessageQueue"); 204 } catch (IllegalStateException expected) { 205 } 206 } 207 208 @Test postAndRemoveSyncBarrierToken_messageBefore()209 public void postAndRemoveSyncBarrierToken_messageBefore() { 210 enqueueMessage(testMessage, SystemClock.uptimeMillis()); 211 int token = postSyncBarrier(queue); 212 removeSyncBarrier(queue, token); 213 214 assertThat(shadowQueue.getHead()).isEqualTo(testMessage); 215 } 216 217 @Test postAndRemoveSyncBarrierToken_messageBeforeConsumed()218 public void postAndRemoveSyncBarrierToken_messageBeforeConsumed() { 219 enqueueMessage(testMessage, SystemClock.uptimeMillis()); 220 int token = postSyncBarrier(queue); 221 scheduler.advanceToLastPostedRunnable(); 222 removeSyncBarrier(queue, token); 223 assertThat(shadowQueue.getHead()).isNull(); 224 assertThat(handler.handled).named("handled:after").containsExactly(testMessage); 225 } 226 227 @Test postAndRemoveSyncBarrierToken_messageAfter()228 public void postAndRemoveSyncBarrierToken_messageAfter() { 229 enqueueMessage(testMessage, SystemClock.uptimeMillis() + 100); 230 int token = postSyncBarrier(queue); 231 removeSyncBarrier(queue, token); 232 233 assertThat(shadowQueue.getHead()).isEqualTo(testMessage); 234 scheduler.advanceToLastPostedRunnable(); 235 assertThat(shadowQueue.getHead()).isNull(); 236 assertThat(handler.handled).named("handled:after").containsExactly(testMessage); 237 } 238 239 @Test postAndRemoveSyncBarrierToken_syncBefore()240 public void postAndRemoveSyncBarrierToken_syncBefore() { 241 int token = postSyncBarrier(queue); 242 enqueueMessage(testMessage, SystemClock.uptimeMillis()); 243 scheduler.advanceToLastPostedRunnable(); 244 removeSyncBarrier(queue, token); 245 assertThat(shadowQueue.getHead()).isNull(); 246 assertThat(handler.handled).named("handled:after").containsExactly(testMessage); 247 } 248 removeSyncBarrier(MessageQueue queue, int token)249 private static void removeSyncBarrier(MessageQueue queue, int token) { 250 ReflectionHelpers.callInstanceMethod( 251 MessageQueue.class, queue, "removeSyncBarrier", from(int.class, token)); 252 } 253 postSyncBarrier(MessageQueue queue)254 private static int postSyncBarrier(MessageQueue queue) { 255 if (RuntimeEnvironment.getApiLevel() >= M) { 256 return queue.postSyncBarrier(); 257 } else { 258 return ReflectionHelpers.callInstanceMethod( 259 MessageQueue.class, 260 queue, 261 "enqueueSyncBarrier", 262 from(long.class, SystemClock.uptimeMillis())); 263 } 264 } 265 } 266