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