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