1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
4 import static android.os.Build.VERSION_CODES.M;
5 import static com.google.common.base.Preconditions.checkState;
6 import static org.robolectric.RuntimeEnvironment.getApiLevel;
7 import static org.robolectric.util.reflector.Reflector.reflector;
8 
9 import android.os.Looper;
10 import android.os.Message;
11 import android.os.MessageQueue;
12 import android.os.MessageQueue.IdleHandler;
13 import android.os.SystemClock;
14 import android.util.Log;
15 import com.android.internal.annotations.VisibleForTesting;
16 import com.google.common.base.Predicate;
17 import java.time.Duration;
18 import java.util.ArrayList;
19 import org.robolectric.annotation.Implementation;
20 import org.robolectric.annotation.Implements;
21 import org.robolectric.annotation.LooperMode;
22 import org.robolectric.annotation.RealObject;
23 import org.robolectric.res.android.NativeObjRegistry;
24 import org.robolectric.shadow.api.Shadow;
25 import org.robolectric.shadows.ShadowMessage.MessageReflector;
26 import org.robolectric.util.Scheduler;
27 import org.robolectric.util.reflector.Accessor;
28 import org.robolectric.util.reflector.Direct;
29 import org.robolectric.util.reflector.ForType;
30 import org.robolectric.versioning.AndroidVersions.V;
31 
32 /**
33  * The shadow {@link} MessageQueue} for {@link LooperMode.Mode.PAUSED}
34  *
35  * <p>This class should not be referenced directly. Use {@link ShadowMessageQueue} instead.
36  */
37 @SuppressWarnings("SynchronizeOnNonFinalField")
38 @Implements(value = MessageQueue.class, isInAndroidSdk = false)
39 public class ShadowPausedMessageQueue extends ShadowMessageQueue {
40 
41   @RealObject private MessageQueue realQueue;
42 
43   // just use this class as the native object
44   private static NativeObjRegistry<ShadowPausedMessageQueue> nativeQueueRegistry =
45       new NativeObjRegistry<ShadowPausedMessageQueue>(ShadowPausedMessageQueue.class);
46   private boolean isPolling = false;
47   private ShadowPausedSystemClock.Listener clockListener;
48   private Exception uncaughtException = null;
49 
50   // shadow constructor instead of nativeInit because nativeInit signature has changed across SDK
51   // versions
52   @Implementation
__constructor__(boolean quitAllowed)53   protected void __constructor__(boolean quitAllowed) {
54     reflector(MessageQueueReflector.class, realQueue).__constructor__(quitAllowed);
55     long ptr = nativeQueueRegistry.register(this);
56     reflector(MessageQueueReflector.class, realQueue).setPtr(ptr);
57     clockListener =
58         () -> {
59           synchronized (realQueue) {
60             // only wake up the Looper thread if queue is non empty to reduce contention if many
61             // Looper threads are active
62             if (getMessages() != null) {
63               nativeWake(ptr);
64             }
65           }
66         };
67     ShadowPausedSystemClock.addStaticListener(clockListener);
68   }
69 
70   @Implementation
nativeDestroy(long ptr)71   protected static void nativeDestroy(long ptr) {
72     ShadowPausedMessageQueue q = nativeQueueRegistry.unregister(ptr);
73     ShadowPausedSystemClock.removeListener(q.clockListener);
74   }
75 
76   @Implementation(maxSdk = LOLLIPOP_MR1, methodName = "nativePollOnce")
nativePollOncePreM(long ptr, int timeoutMillis)77   protected static void nativePollOncePreM(long ptr, int timeoutMillis) {
78     nativeQueueRegistry.getNativeObject(ptr).nativePollOnce(ptr, timeoutMillis);
79   }
80 
81   @Implementation(minSdk = M)
nativePollOnce(long ptr, int timeoutMillis)82   protected void nativePollOnce(long ptr, int timeoutMillis) {
83     if (timeoutMillis == 0) {
84       return;
85     }
86     synchronized (realQueue) {
87       // only block if queue is empty
88       // ignore timeout since clock is not advancing. ClockListener will notify when clock advances
89       while (isIdle() && !isQuitting()) {
90         isPolling = true;
91         try {
92           realQueue.wait();
93         } catch (InterruptedException e) {
94           // ignore
95         }
96       }
97       isPolling = false;
98     }
99   }
100 
101   /**
102    * Polls the message queue waiting until a message is posted to the head of the queue. This will
103    * suspend the thread until a new message becomes available. Returns immediately if the queue is
104    * not idle. There's no guarantee that the message queue will not still be idle when returning,
105    * but if the message queue becomes not idle it will return immediately.
106    *
107    * <p>See {@link ShadowPausedLooper#poll(long)} for more information.
108    *
109    * @param timeout Timeout in milliseconds, the maximum time to wait before returning, or 0 to wait
110    *     indefinitely,
111    */
poll(long timeout)112   void poll(long timeout) {
113     checkState(Looper.myLooper() == Looper.getMainLooper() && Looper.myQueue() == realQueue);
114     // Message queue typically expects the looper to loop calling next() which returns current
115     // messages from the head of the queue. If no messages are current it will mark itself blocked
116     // and call nativePollOnce (see above) which suspends the thread until the next message's time.
117     // When messages are posted to the queue, if a new message is posted to the head and the queue
118     // is marked as blocked, then the enqueue function will notify and resume next(), allowing it
119     // return the next message. To simulate this behavior check if the queue is idle and if it is
120     // mark the queue as blocked and wait on a new message.
121     synchronized (realQueue) {
122       if (isIdle()) {
123         reflector(MessageQueueReflector.class, realQueue).setBlocked(true);
124         try {
125           realQueue.wait(timeout);
126         } catch (InterruptedException ignored) {
127           // Fall through and unblock with no messages.
128         } finally {
129           reflector(MessageQueueReflector.class, realQueue).setBlocked(false);
130         }
131       }
132     }
133   }
134 
135   @Implementation
nativeWake(long ptr)136   protected static void nativeWake(long ptr) {
137     MessageQueue realQueue = nativeQueueRegistry.getNativeObject(ptr).realQueue;
138     synchronized (realQueue) {
139       realQueue.notifyAll();
140     }
141   }
142 
143   @Implementation(minSdk = M)
nativeIsPolling(long ptr)144   protected static boolean nativeIsPolling(long ptr) {
145     return nativeQueueRegistry.getNativeObject(ptr).isPolling;
146   }
147 
148   /** Exposes the API23+_isIdle method to older platforms */
149   @Implementation(minSdk = 23)
isIdle()150   public boolean isIdle() {
151     synchronized (realQueue) {
152       Message msg = peekNextExecutableMessage();
153       if (msg == null) {
154         return true;
155       }
156 
157       long now = SystemClock.uptimeMillis();
158       long when = shadowOfMsg(msg).getWhen();
159       return now < when;
160     }
161   }
162 
peekNextExecutableMessage()163   Message peekNextExecutableMessage() {
164     MessageQueueReflector internalQueue = reflector(MessageQueueReflector.class, realQueue);
165     Message msg = internalQueue.getMessages();
166 
167     if (msg != null && shadowOfMsg(msg).getTarget() == null) {
168       // Stalled by a barrier.  Find the next asynchronous message in the queue.
169       do {
170         msg = shadowOfMsg(msg).internalGetNext();
171       } while (msg != null && !msg.isAsynchronous());
172     }
173 
174     return msg;
175   }
176 
getNext()177   Message getNext() {
178     return reflector(MessageQueueReflector.class, realQueue).next();
179   }
180 
isQuitAllowed()181   boolean isQuitAllowed() {
182     return reflector(MessageQueueReflector.class, realQueue).getQuitAllowed();
183   }
184 
185   @VisibleForTesting
doEnqueueMessage(Message msg, long when)186   void doEnqueueMessage(Message msg, long when) {
187     enqueueMessage(msg, when);
188   }
189 
190   @Implementation
enqueueMessage(Message msg, long when)191   protected boolean enqueueMessage(Message msg, long when) {
192     synchronized (realQueue) {
193       if (uncaughtException != null) {
194         // looper thread has died
195         IllegalStateException e =
196             new IllegalStateException(
197                 msg.getTarget()
198                     + " sending message to a Looper thread that has died due to an uncaught"
199                     + " exception",
200                 uncaughtException);
201         Log.w("ShadowPausedMessageQueue", e);
202         msg.recycle();
203         throw e;
204       }
205       return reflector(MessageQueueReflector.class, realQueue).enqueueMessage(msg, when);
206     }
207   }
208 
getMessages()209   Message getMessages() {
210     return reflector(MessageQueueReflector.class, realQueue).getMessages();
211   }
212 
213   @Implementation(minSdk = M)
isPolling()214   protected boolean isPolling() {
215     synchronized (realQueue) {
216       return isPolling;
217     }
218   }
219 
quit()220   void quit() {
221     quit(true);
222   }
223 
224   @Implementation
quit(boolean allowed)225   protected void quit(boolean allowed) {
226     reflector(MessageQueueReflector.class, realQueue).quit(allowed);
227     ShadowPausedSystemClock.removeListener(clockListener);
228   }
229 
isQuitting()230   boolean isQuitting() {
231     return reflector(MessageQueueReflector.class, realQueue).getQuitting();
232   }
233 
getNextScheduledTaskTime()234   Duration getNextScheduledTaskTime() {
235     Message next = peekNextExecutableMessage();
236 
237     if (next == null) {
238       return Duration.ZERO;
239     }
240     return Duration.ofMillis(convertWhenToScheduledTime(shadowOfMsg(next).getWhen()));
241   }
242 
getLastScheduledTaskTime()243   Duration getLastScheduledTaskTime() {
244     long when = 0;
245     synchronized (realQueue) {
246       Message next = getMessages();
247       if (next == null) {
248         return Duration.ZERO;
249       }
250       while (next != null) {
251         if (next.getTarget() != null) {
252           when = shadowOfMsg(next).getWhen();
253         }
254         next = shadowOfMsg(next).internalGetNext();
255       }
256     }
257     return Duration.ofMillis(convertWhenToScheduledTime(when));
258   }
259 
convertWhenToScheduledTime(long when)260   private static long convertWhenToScheduledTime(long when) {
261     // in some situations, when can be 0 or less than uptimeMillis. Always floor it to at least
262     // convertWhenToUptime
263     if (when < SystemClock.uptimeMillis()) {
264       when = SystemClock.uptimeMillis();
265     }
266     return when;
267   }
268 
269   /**
270    * Internal method to get the number of entries in the MessageQueue.
271    *
272    * <p>Do not use, will likely be removed in a future release.
273    */
internalGetSize()274   public int internalGetSize() {
275     int count = 0;
276     synchronized (realQueue) {
277       Message next = getMessages();
278       while (next != null) {
279         if (next.getTarget() != null) {
280           count++;
281         }
282         next = shadowOfMsg(next).internalGetNext();
283       }
284     }
285     return count;
286   }
287 
288   /**
289    * Returns the message at the head of the queue immediately, regardless of its scheduled time.
290    * Compare to {@link #getNext()} which will only return the next message if the system clock is
291    * advanced to its scheduled time.
292    *
293    * <p>This is a copy of the real MessageQueue.next implementation with the 'when' handling logic
294    * omitted.
295    */
getNextIgnoringWhen()296   Message getNextIgnoringWhen() {
297     MessageQueueReflector queueReflector = reflector(MessageQueueReflector.class, realQueue);
298     synchronized (realQueue) {
299       Message prevMsg = null;
300       Message msg = getMessages();
301       // Head is blocked on synchronization barrier, find next asynchronous message.
302       if (msg != null && msg.getTarget() == null) {
303         do {
304           prevMsg = msg;
305           msg = shadowOfMsg(msg).internalGetNext();
306         } while (msg != null && !msg.isAsynchronous());
307       }
308       if (msg != null) {
309         Message nextMsg = reflector(MessageReflector.class, msg).getNext();
310         if (prevMsg != null) {
311           reflector(MessageReflector.class, prevMsg).setNext(nextMsg);
312           if (reflector(MessageReflector.class, prevMsg).getNext() == null
313               && getApiLevel() >= V.SDK_INT) {
314             queueReflector.setLast(prevMsg);
315           }
316         } else {
317           queueReflector.setMessages(nextMsg);
318           if (nextMsg == null && getApiLevel() >= V.SDK_INT) {
319             queueReflector.setLast(null);
320           }
321         }
322         if (msg.isAsynchronous() && getApiLevel() >= V.SDK_INT) {
323             queueReflector.setAsyncMessageCount(queueReflector.getAsyncMessageCount() - 1);
324         }
325       }
326       return msg;
327     }
328   }
329 
330   // TODO: reconsider exposing this as a public API. Only ShadowPausedLooper needs to access this,
331   // so it should be package private
332   @Override
reset()333   public void reset() {
334     MessageQueueReflector msgQueue = reflector(MessageQueueReflector.class, realQueue);
335     synchronized (realQueue) {
336       msgQueue.setMessages(null);
337       msgQueue.setIdleHandlers(new ArrayList<>());
338       msgQueue.setNextBarrierToken(0);
339       if (getApiLevel() >= V.SDK_INT) {
340         msgQueue.setLast(null);
341         msgQueue.setAsyncMessageCount(0);
342       }
343     }
344     setUncaughtException(null);
345   }
346 
shadowOfMsg(Message head)347   private static ShadowPausedMessage shadowOfMsg(Message head) {
348     return Shadow.extract(head);
349   }
350 
351   @Override
getScheduler()352   public Scheduler getScheduler() {
353     throw new UnsupportedOperationException("Not supported in PAUSED LooperMode.");
354   }
355 
356   @Override
setScheduler(Scheduler scheduler)357   public void setScheduler(Scheduler scheduler) {
358     throw new UnsupportedOperationException("Not supported in PAUSED LooperMode.");
359   }
360 
361   // intentionally do not support direct access to MessageQueue internals
362 
363   @Override
getHead()364   public Message getHead() {
365     throw new UnsupportedOperationException("Not supported in PAUSED LooperMode.");
366   }
367 
368   @Override
setHead(Message msg)369   public void setHead(Message msg) {
370     throw new UnsupportedOperationException("Not supported in PAUSED LooperMode.");
371   }
372 
373   /**
374    * Retrieves a copy of the current list of idle handlers. Idle handlers are read with
375    * synchronization on the real queue.
376    */
getIdleHandlersCopy()377   ArrayList<IdleHandler> getIdleHandlersCopy() {
378     synchronized (realQueue) {
379       return new ArrayList<>(reflector(MessageQueueReflector.class, realQueue).getIdleHandlers());
380     }
381   }
382 
383   /**
384    * Called when an uncaught exception occurred in this message queue's Looper thread.
385    *
386    * <p>In real android, by default an exception handler is installed which kills the entire process
387    * when an uncaught exception occurs. We don't want to do this in robolectric to isolate tests, so
388    * instead an uncaught exception puts the message queue into an error state, where any future
389    * interaction will rethrow the exception.
390    */
setUncaughtException(Exception e)391   void setUncaughtException(Exception e) {
392     synchronized (realQueue) {
393       this.uncaughtException = e;
394     }
395   }
396 
hasUncaughtException()397   boolean hasUncaughtException() {
398     synchronized (realQueue) {
399       return uncaughtException != null;
400     }
401   }
402 
checkQueueState()403   void checkQueueState() {
404     synchronized (realQueue) {
405       if (uncaughtException != null) {
406         throw new IllegalStateException(
407             "Looper thread that has died due to an uncaught exception", uncaughtException);
408       }
409     }
410   }
411 
412   /**
413    * Remove all messages from queue
414    *
415    * @param msgProcessor a callback to apply to each mesg
416    */
drainQueue(Predicate<Runnable> msgProcessor)417   void drainQueue(Predicate<Runnable> msgProcessor) {
418     synchronized (realQueue) {
419       Message msg = getMessages();
420       while (msg != null) {
421         boolean unused = msgProcessor.apply(msg.getCallback());
422         Message next = shadowOfMsg(msg).internalGetNext();
423         shadowOfMsg(msg).recycleUnchecked();
424         msg = next;
425       }
426       reflector(MessageQueueReflector.class, realQueue).setMessages(null);
427       if (getApiLevel() >= V.SDK_INT) {
428         reflector(MessageQueueReflector.class, realQueue).setLast(null);
429         reflector(MessageQueueReflector.class, realQueue).setAsyncMessageCount(0);
430       }
431     }
432   }
433 
434   /** Accessor interface for {@link MessageQueue}'s internals. */
435   @ForType(MessageQueue.class)
436   private interface MessageQueueReflector {
437     @Direct
__constructor__(boolean quitAllowed)438     void __constructor__(boolean quitAllowed);
439 
440     @Direct
enqueueMessage(Message msg, long when)441     boolean enqueueMessage(Message msg, long when);
442 
next()443     Message next();
444 
445     @Accessor("mMessages")
setMessages(Message msg)446     void setMessages(Message msg);
447 
448     @Accessor("mMessages")
getMessages()449     Message getMessages();
450 
451     @Accessor("mIdleHandlers")
setIdleHandlers(ArrayList<IdleHandler> list)452     void setIdleHandlers(ArrayList<IdleHandler> list);
453 
454     @Accessor("mIdleHandlers")
getIdleHandlers()455     ArrayList<IdleHandler> getIdleHandlers();
456 
457     @Accessor("mNextBarrierToken")
setNextBarrierToken(int token)458     void setNextBarrierToken(int token);
459 
460     @Accessor("mQuitAllowed")
getQuitAllowed()461     boolean getQuitAllowed();
462 
463     @Accessor("mPtr")
setPtr(long ptr)464     void setPtr(long ptr);
465 
466     @Direct
quit(boolean b)467     void quit(boolean b);
468 
469     @Accessor("mQuitting")
getQuitting()470     boolean getQuitting();
471 
472     @Accessor("mLast")
setLast(Message msg)473     void setLast(Message msg);
474 
475     @Accessor("mAsyncMessageCount")
getAsyncMessageCount()476     int getAsyncMessageCount();
477 
478     @Accessor("mAsyncMessageCount")
setAsyncMessageCount(int asyncMessageCount)479     void setAsyncMessageCount(int asyncMessageCount);
480 
481     @Accessor("mBlocked")
setBlocked(boolean blocked)482     void setBlocked(boolean blocked);
483   }
484 }
485