1 package org.robolectric.shadows; 2 3 import static com.google.common.base.Preconditions.checkState; 4 import static org.robolectric.shadow.api.Shadow.invokeConstructor; 5 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 6 import static org.robolectric.util.reflector.Reflector.reflector; 7 8 import android.app.Instrumentation; 9 import android.os.ConditionVariable; 10 import android.os.Handler; 11 import android.os.Looper; 12 import android.os.Message; 13 import android.os.MessageQueue.IdleHandler; 14 import android.os.SystemClock; 15 import android.util.Log; 16 import com.google.common.annotations.VisibleForTesting; 17 import com.google.common.base.Preconditions; 18 import java.time.Duration; 19 import java.util.ArrayList; 20 import java.util.Collection; 21 import java.util.Collections; 22 import java.util.List; 23 import java.util.Set; 24 import java.util.WeakHashMap; 25 import java.util.concurrent.CountDownLatch; 26 import java.util.concurrent.Executor; 27 import java.util.concurrent.LinkedBlockingQueue; 28 import java.util.concurrent.TimeUnit; 29 import javax.annotation.concurrent.GuardedBy; 30 import org.robolectric.RuntimeEnvironment; 31 import org.robolectric.annotation.Implementation; 32 import org.robolectric.annotation.Implements; 33 import org.robolectric.annotation.LooperMode; 34 import org.robolectric.annotation.LooperMode.Mode; 35 import org.robolectric.annotation.RealObject; 36 import org.robolectric.annotation.Resetter; 37 import org.robolectric.config.ConfigurationRegistry; 38 import org.robolectric.shadow.api.Shadow; 39 import org.robolectric.util.Scheduler; 40 import org.robolectric.util.reflector.Accessor; 41 import org.robolectric.util.reflector.Direct; 42 import org.robolectric.util.reflector.ForType; 43 import org.robolectric.util.reflector.Static; 44 45 /** 46 * The shadow Looper for {@link LooperMode.Mode.PAUSED and @link 47 * LooperMode.Mode.INSTRUMENTATION_TEST}. 48 * 49 * <p>This shadow differs from the legacy {@link ShadowLegacyLooper} in the following ways:\ - Has 50 * no connection to {@link org.robolectric.util.Scheduler}. Its APIs are standalone - The main 51 * looper is always paused in PAUSED MODE but can be unpaused in INSTRUMENTATION_TEST mode. When a 52 * looper is paused, posted messages to it are not executed unless {@link #idle()} is called. - Just 53 * like in real Android, each looper has its own thread, and posted tasks get executed in that 54 * thread. - - There is only a single {@link SystemClock} value that all loopers read from. Unlike 55 * legacy behavior where each {@link org.robolectric.util.Scheduler} kept their own clock value. 56 * 57 * <p>This class should not be used directly; use {@link ShadowLooper} instead. 58 */ 59 @Implements( 60 value = Looper.class, 61 // turn off shadowOf generation. 62 isInAndroidSdk = false) 63 @SuppressWarnings("NewApi") 64 public final class ShadowPausedLooper extends ShadowLooper { 65 66 // Keep reference to all created Loopers so they can be torn down after test 67 private static Set<Looper> loopingLoopers = 68 Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<Looper, Boolean>())); 69 70 private static boolean ignoreUncaughtExceptions = false; 71 72 @RealObject private Looper realLooper; 73 private boolean isPaused = false; 74 // the Executor that executes looper messages. Must be written to on looper thread 75 private Executor looperExecutor; 76 77 @Implementation __constructor__(boolean quitAllowed)78 protected void __constructor__(boolean quitAllowed) { 79 invokeConstructor(Looper.class, realLooper, from(boolean.class, quitAllowed)); 80 81 loopingLoopers.add(realLooper); 82 looperExecutor = new HandlerExecutor(realLooper); 83 } 84 getLoopers()85 protected static Collection<Looper> getLoopers() { 86 List<Looper> loopers = new ArrayList<>(loopingLoopers); 87 return Collections.unmodifiableCollection(loopers); 88 } 89 90 @Override quitUnchecked()91 public void quitUnchecked() { 92 throw new UnsupportedOperationException( 93 "this action is not" + " supported" + " in " + looperMode() + " mode."); 94 } 95 96 @Override hasQuit()97 public boolean hasQuit() { 98 throw new UnsupportedOperationException( 99 "this action is not" + " supported" + " in " + looperMode() + " mode."); 100 } 101 102 @Override idle()103 public void idle() { 104 executeOnLooper(new IdlingRunnable()); 105 } 106 107 @Override idleFor(long time, TimeUnit timeUnit)108 public void idleFor(long time, TimeUnit timeUnit) { 109 long endingTimeMs = SystemClock.uptimeMillis() + timeUnit.toMillis(time); 110 long nextScheduledTimeMs = getNextScheduledTaskTime().toMillis(); 111 while (nextScheduledTimeMs != 0 && nextScheduledTimeMs <= endingTimeMs) { 112 SystemClock.setCurrentTimeMillis(nextScheduledTimeMs); 113 idle(); 114 nextScheduledTimeMs = getNextScheduledTaskTime().toMillis(); 115 } 116 SystemClock.setCurrentTimeMillis(endingTimeMs); 117 // the last SystemClock update might have added new tasks to the main looper via Choreographer 118 // so idle once more. 119 idle(); 120 } 121 122 @Override isIdle()123 public boolean isIdle() { 124 if (Thread.currentThread() == realLooper.getThread() || isPaused) { 125 return shadowQueue().isIdle(); 126 } else { 127 return shadowQueue().isIdle() && shadowQueue().isPolling(); 128 } 129 } 130 131 @Override unPause()132 public void unPause() { 133 if (realLooper == Looper.getMainLooper() 134 && looperMode() != LooperMode.Mode.INSTRUMENTATION_TEST) { 135 throw new UnsupportedOperationException("main looper cannot be unpaused"); 136 } 137 executeOnLooper(new UnPauseRunnable()); 138 } 139 140 @Override pause()141 public void pause() { 142 if (!isPaused()) { 143 executeOnLooper(new PausedLooperExecutor()); 144 } 145 } 146 147 @Override isPaused()148 public boolean isPaused() { 149 return isPaused; 150 } 151 152 @Override setPaused(boolean shouldPause)153 public boolean setPaused(boolean shouldPause) { 154 if (shouldPause) { 155 pause(); 156 } else { 157 unPause(); 158 } 159 return true; 160 } 161 162 @Override resetScheduler()163 public void resetScheduler() { 164 throw new UnsupportedOperationException( 165 "this action is not" + " supported" + " in " + looperMode() + " mode."); 166 } 167 168 @Override reset()169 public void reset() { 170 throw new UnsupportedOperationException( 171 "this action is not" + " supported" + " in " + looperMode() + " mode."); 172 } 173 174 @Override idleIfPaused()175 public void idleIfPaused() { 176 if (isPaused()) { 177 idle(); 178 } 179 } 180 181 @Override idleConstantly(boolean shouldIdleConstantly)182 public void idleConstantly(boolean shouldIdleConstantly) { 183 throw new UnsupportedOperationException( 184 "this action is not" + " supported" + " in " + looperMode() + " mode."); 185 } 186 187 @Override runToEndOfTasks()188 public void runToEndOfTasks() { 189 idleFor(Duration.ofMillis(getLastScheduledTaskTime().toMillis() - SystemClock.uptimeMillis())); 190 } 191 192 @Override runToNextTask()193 public void runToNextTask() { 194 idleFor(Duration.ofMillis(getNextScheduledTaskTime().toMillis() - SystemClock.uptimeMillis())); 195 } 196 197 @Override runOneTask()198 public void runOneTask() { 199 executeOnLooper(new RunOneRunnable()); 200 } 201 202 @Override post(Runnable runnable, long delayMillis)203 public boolean post(Runnable runnable, long delayMillis) { 204 return new Handler(realLooper).postDelayed(runnable, delayMillis); 205 } 206 207 @Override postAtFrontOfQueue(Runnable runnable)208 public boolean postAtFrontOfQueue(Runnable runnable) { 209 return new Handler(realLooper).postAtFrontOfQueue(runnable); 210 } 211 212 /** 213 * Posts the runnable to the looper and idles until the runnable has been run. Generally clients 214 * should prefer to use {@link Instrumentation#runOnMainSync(Runnable)}, which will reraise 215 * underlying runtime exceptions to the caller. 216 */ postSync(Runnable runnable)217 public void postSync(Runnable runnable) { 218 executeOnLooper(new PostAndIdleToRunnable(runnable)); 219 } 220 221 /** 222 * Posts the runnable as an asynchronous task and wait until it has been run. Ignores all 223 * exceptions. 224 * 225 * <p>This method is similar to postSync, but used in internal cases where you want to make a best 226 * effort quick attempt to execute the Runnable, and do not need to idle all the non-async tasks 227 * that might be posted to the Looper's queue. 228 */ postSyncQuiet(Runnable runnable)229 void postSyncQuiet(Runnable runnable) { 230 try { 231 executeOnLooper(new PostAsyncAndIdleToRunnable(runnable)); 232 } catch (RuntimeException e) { 233 Log.w("ShadowPausedLooper", "ignoring exception on postSyncQuiet", e); 234 } 235 } 236 237 // this API doesn't make sense in LooperMode.PAUSED, but just retain it for backwards 238 // compatibility for now 239 @Override runPaused(Runnable runnable)240 public void runPaused(Runnable runnable) { 241 if (Thread.currentThread() == realLooper.getThread()) { 242 // just run 243 runnable.run(); 244 } else { 245 throw new UnsupportedOperationException( 246 "this method can only be called on " + realLooper.getThread().getName()); 247 } 248 } 249 250 /** 251 * Polls the message queue waiting until a message is posted to the head of the queue. This will 252 * suspend the thread until a new message becomes available. Returns immediately if the queue is 253 * not idle. There's no guarantee that the message queue will not still be idle when returning, 254 * but if the message queue becomes not idle it will return immediately. 255 * 256 * <p>This method is only applicable for the main looper's queue when called on the main thread, 257 * as the main looper in Robolectric is processed manually (it doesn't loop)--looper threads are 258 * using the native polling of their loopers. Throws an exception if called for another looper's 259 * queue. Non-main thread loopers should use {@link #unPause()}. 260 * 261 * <p>This should be used with care, it can be used to suspend the main (i.e. test) thread while 262 * worker threads perform some work, and then resumed by posting to the main looper. Used in a 263 * loop to wait on some condition it can process messages on the main looper, simulating the 264 * behavior of the real looper, for example: 265 * 266 * <pre>{@code 267 * while (!condition) { 268 * shadowMainLooper.poll(timeout); 269 * shadowMainLooper.idle(); 270 * } 271 * }</pre> 272 * 273 * <p>Beware though that a message must be posted to the main thread after the condition is 274 * satisfied, or the condition satisfied while idling the main thread, otherwise the main thread 275 * will continue to be suspended until the timeout. 276 * 277 * @param timeout Timeout in milliseconds, the maximum time to wait before returning, or 0 to wait 278 * indefinitely, 279 */ poll(long timeout)280 public void poll(long timeout) { 281 checkState(Looper.myLooper() == Looper.getMainLooper() && Looper.myLooper() == realLooper); 282 shadowQueue().poll(timeout); 283 } 284 285 @Override getNextScheduledTaskTime()286 public Duration getNextScheduledTaskTime() { 287 return shadowQueue().getNextScheduledTaskTime(); 288 } 289 290 @Override getLastScheduledTaskTime()291 public Duration getLastScheduledTaskTime() { 292 return shadowQueue().getLastScheduledTaskTime(); 293 } 294 295 @Resetter 296 @SuppressWarnings("deprecation") // This is Robolectric library code resetLoopers()297 public static synchronized void resetLoopers() { 298 // Do not use looperMode() here, because its cached value might already have been reset 299 LooperMode.Mode looperMode = ConfigurationRegistry.get(LooperMode.Mode.class); 300 301 if (looperMode == LooperMode.Mode.LEGACY) { 302 return; 303 } 304 305 createMainThreadAndLooperIfNotAlive(); 306 ShadowPausedChoreographer.resetChoreographers(); 307 for (Looper looper : getLoopers()) { 308 ShadowPausedLooper shadowPausedLooper = Shadow.extract(looper); 309 shadowPausedLooper.resetLooperToInitialState(); 310 } 311 } 312 313 private static final Object instrumentationTestMainThreadLock = new Object(); 314 315 @SuppressWarnings("NonFinalStaticField") // State used in static method for main thread. 316 @GuardedBy("instrumentationTestMainThreadLock") 317 private static boolean instrumentationTestMainThreadShouldRestart = false; 318 createMainThreadAndLooperIfNotAlive()319 private static synchronized void createMainThreadAndLooperIfNotAlive() { 320 Looper mainLooper = Looper.getMainLooper(); 321 322 switch (ConfigurationRegistry.get(LooperMode.Mode.class)) { 323 case INSTRUMENTATION_TEST: 324 if (mainLooper == null) { 325 ConditionVariable mainThreadPrepared = new ConditionVariable(); 326 Thread mainThread = 327 new Thread(String.format("SDK %d Main Thread", RuntimeEnvironment.getApiLevel())) { 328 @Override 329 public void run() { 330 Looper.prepareMainLooper(); 331 mainThreadPrepared.open(); 332 while (true) { 333 try { 334 Looper.loop(); 335 } catch (Throwable e) { 336 // The exception is handled inside of the loop shadow method, so ignore it. 337 } 338 // Wait to restart the looper until the looper is reset. 339 synchronized (instrumentationTestMainThreadLock) { 340 while (!instrumentationTestMainThreadShouldRestart) { 341 try { 342 instrumentationTestMainThreadLock.wait(); 343 } catch (InterruptedException ie) { 344 // Shouldn't be interrupted, continue waiting for reset signal. 345 } 346 } 347 instrumentationTestMainThreadShouldRestart = false; 348 } 349 } 350 } 351 }; 352 mainThread.start(); 353 mainThreadPrepared.block(); 354 Thread.currentThread() 355 .setName(String.format("SDK %d Test Thread", RuntimeEnvironment.getApiLevel())); 356 } else { 357 ShadowPausedMessageQueue shadowQueue = Shadow.extract(mainLooper.getQueue()); 358 if (shadowQueue.hasUncaughtException()) { 359 shadowQueue.reset(); 360 synchronized (instrumentationTestMainThreadLock) { 361 // If the looper died in a previous test it will be waiting to restart, notify it. 362 instrumentationTestMainThreadShouldRestart = true; 363 instrumentationTestMainThreadLock.notify(); 364 } 365 } 366 } 367 break; 368 case PAUSED: 369 if (Looper.myLooper() == null) { 370 Looper.prepareMainLooper(); 371 } 372 break; 373 default: 374 throw new UnsupportedOperationException( 375 "Only supports INSTRUMENTATION_TEST and PAUSED LooperMode."); 376 } 377 } 378 379 @VisibleForTesting resetLooperToInitialState()380 synchronized void resetLooperToInitialState() { 381 // Do not use looperMode() here, because its cached value might already have been reset 382 LooperMode.Mode looperMode = ConfigurationRegistry.get(LooperMode.Mode.class); 383 384 ShadowPausedMessageQueue shadowQueue = Shadow.extract(realLooper.getQueue()); 385 shadowQueue.reset(); 386 387 if (realLooper.getThread().isAlive() 388 && !shadowQueue 389 .isQuitting()) { // Trying to unpause a quitted background Looper may deadlock. 390 391 if (isPaused() 392 && !(realLooper == Looper.getMainLooper() && looperMode != Mode.INSTRUMENTATION_TEST)) { 393 unPause(); 394 } 395 } 396 } 397 398 @Implementation prepareMainLooper()399 protected static void prepareMainLooper() { 400 reflector(LooperReflector.class).prepareMainLooper(); 401 ShadowPausedLooper pausedLooper = Shadow.extract(Looper.getMainLooper()); 402 pausedLooper.isPaused = looperMode() == Mode.PAUSED; 403 } 404 405 @Implementation quit()406 protected void quit() { 407 if (isPaused()) { 408 executeOnLooper(new UnPauseRunnable()); 409 } 410 synchronized (realLooper.getQueue()) { 411 drainQueueSafely(shadowQueue()); 412 reflector(LooperReflector.class, realLooper).quit(); 413 } 414 } 415 416 @Implementation quitSafely()417 protected void quitSafely() { 418 if (isPaused()) { 419 executeOnLooper(new UnPauseRunnable()); 420 } 421 reflector(LooperReflector.class, realLooper).quitSafely(); 422 } 423 424 @Override getScheduler()425 public Scheduler getScheduler() { 426 throw new UnsupportedOperationException( 427 String.format("this action is not supported in %s mode.", looperMode())); 428 } 429 shadowMsg(Message msg)430 private static ShadowPausedMessage shadowMsg(Message msg) { 431 return Shadow.extract(msg); 432 } 433 shadowQueue()434 private ShadowPausedMessageQueue shadowQueue() { 435 return Shadow.extract(realLooper.getQueue()); 436 } 437 setLooperExecutor(Executor executor)438 private void setLooperExecutor(Executor executor) { 439 looperExecutor = executor; 440 } 441 442 /** Retrieves the next message or null if the queue is idle. */ getNextExecutableMessage()443 private Message getNextExecutableMessage() { 444 synchronized (realLooper.getQueue()) { 445 // Use null if the queue is idle, otherwise getNext() will block. 446 return shadowQueue().isIdle() ? null : shadowQueue().getNext(); 447 } 448 } 449 450 /** 451 * By default Robolectric will put Loopers that throw uncaught exceptions in their loop method 452 * into an error state, where any future posting to the looper's queue will throw an error. 453 * 454 * <p>This API allows you to disable this behavior. Note this is a permanent setting - it is not 455 * reset between tests. 456 * 457 * @deprecated this method only exists to accommodate legacy tests with preexisting issues. 458 * Silently discarding exceptions is not recommended, and can lead to deadlocks. 459 */ 460 @Deprecated setIgnoreUncaughtExceptions(boolean shouldIgnore)461 public static void setIgnoreUncaughtExceptions(boolean shouldIgnore) { 462 ignoreUncaughtExceptions = shouldIgnore; 463 } 464 465 /** 466 * Shadow loop to handle uncaught exceptions. Without this logic an uncaught exception on a looper 467 * thread will cause idle() to deadlock. 468 */ 469 @Implementation loop()470 protected static void loop() { 471 try { 472 reflector(LooperReflector.class).loop(); 473 } catch (Exception e) { 474 Looper realLooper = Preconditions.checkNotNull(Looper.myLooper()); 475 ShadowPausedMessageQueue shadowQueue = Shadow.extract(realLooper.getQueue()); 476 477 if (ignoreUncaughtExceptions) { 478 // ignore 479 } else { 480 synchronized (realLooper.getQueue()) { 481 shadowQueue.setUncaughtException(e); 482 drainQueueSafely(shadowQueue); 483 } 484 } 485 if (e instanceof ControlException) { 486 ((ControlException) e).rethrowCause(); 487 } else { 488 throw e; 489 } 490 } 491 } 492 drainQueueSafely(ShadowPausedMessageQueue shadowQueue)493 private static void drainQueueSafely(ShadowPausedMessageQueue shadowQueue) { 494 // release any ControlRunnables currently in queue to prevent deadlocks 495 shadowQueue.drainQueue( 496 input -> { 497 if (input instanceof ControlRunnable) { 498 ((ControlRunnable) input).runLatch.countDown(); 499 return true; 500 } 501 return false; 502 }); 503 } 504 505 /** 506 * If the given {@code lastMessageRead} is not null and the queue is now idle, get the idle 507 * handlers and run them. This synchronization mirrors what happens in the real message queue 508 * next() method, but does not block after running the idle handlers. 509 */ triggerIdleHandlersIfNeeded(Message lastMessageRead)510 private void triggerIdleHandlersIfNeeded(Message lastMessageRead) { 511 List<IdleHandler> idleHandlers; 512 // Mirror the synchronization of MessageQueue.next(). If a message was read on the last call 513 // to next() and the queue is now idle, make a copy of the idle handlers and release the lock. 514 // Run the idle handlers without holding the lock, removing those that return false from their 515 // queueIdle() method. 516 synchronized (realLooper.getQueue()) { 517 if (lastMessageRead == null || !shadowQueue().isIdle()) { 518 return; 519 } 520 idleHandlers = shadowQueue().getIdleHandlersCopy(); 521 } 522 for (IdleHandler idleHandler : idleHandlers) { 523 if (!idleHandler.queueIdle()) { 524 // This method already has synchronization internally. 525 realLooper.getQueue().removeIdleHandler(idleHandler); 526 } 527 } 528 } 529 530 /** 531 * An exception raised by a {@link ControlRunnable} if the runnable was interrupted with an 532 * exception. The looper must call {@link #rethrowCause()} after performing cleanup associated 533 * with handling the exception. 534 */ 535 private static final class ControlException extends RuntimeException { 536 private final ControlRunnable controlRunnable; 537 ControlException(ControlRunnable controlRunnable, RuntimeException cause)538 ControlException(ControlRunnable controlRunnable, RuntimeException cause) { 539 super(cause); 540 this.controlRunnable = controlRunnable; 541 } 542 rethrowCause()543 void rethrowCause() { 544 // Release the control runnable only once the looper has finished draining to avoid any 545 // races on the thread that posted the control runnable (otherwise the calling thread may 546 // have subsequent interactions with the looper that result in inconsistent state). 547 controlRunnable.runLatch.countDown(); 548 throw (RuntimeException) getCause(); 549 } 550 } 551 552 /** A runnable that changes looper state, and that must be run from looper's thread */ 553 private abstract static class ControlRunnable implements Runnable { 554 555 protected final CountDownLatch runLatch = new CountDownLatch(1); 556 private volatile RuntimeException exception; 557 558 @Override run()559 public void run() { 560 boolean controlExceptionThrown = false; 561 try { 562 doRun(); 563 } catch (RuntimeException e) { 564 if (!ignoreUncaughtExceptions) { 565 exception = e; 566 } 567 controlExceptionThrown = true; 568 throw new ControlException(this, e); 569 } finally { 570 if (!controlExceptionThrown) { 571 runLatch.countDown(); 572 } 573 } 574 } 575 doRun()576 protected abstract void doRun() throws RuntimeException; 577 waitTillComplete()578 public void waitTillComplete() throws RuntimeException { 579 try { 580 runLatch.await(); 581 } catch (InterruptedException e) { 582 Log.w("ShadowPausedLooper", "wait till idle interrupted"); 583 } 584 if (exception != null) { 585 throw exception; 586 } 587 } 588 } 589 590 private class IdlingRunnable extends ControlRunnable { 591 592 @Override doRun()593 public void doRun() { 594 while (true) { 595 Message msg = getNextExecutableMessage(); 596 if (msg == null) { 597 break; 598 } 599 msg.getTarget().dispatchMessage(msg); 600 shadowMsg(msg).recycleUnchecked(); 601 triggerIdleHandlersIfNeeded(msg); 602 } 603 } 604 } 605 606 private class RunOneRunnable extends ControlRunnable { 607 608 @Override doRun()609 public void doRun() { 610 611 Message msg = shadowQueue().getNextIgnoringWhen(); 612 if (msg != null) { 613 SystemClock.setCurrentTimeMillis(shadowMsg(msg).getWhen()); 614 msg.getTarget().dispatchMessage(msg); 615 triggerIdleHandlersIfNeeded(msg); 616 } 617 } 618 } 619 620 /** 621 * Control runnable that posts the provided runnable to the queue and then idles up to and 622 * including the posted runnable. Provides essentially similar functionality to {@link 623 * Instrumentation#runOnMainSync(Runnable)}. 624 */ 625 private class PostAndIdleToRunnable extends ControlRunnable { 626 private final Runnable runnable; 627 PostAndIdleToRunnable(Runnable runnable)628 PostAndIdleToRunnable(Runnable runnable) { 629 this.runnable = runnable; 630 } 631 632 @Override doRun()633 public void doRun() { 634 new Handler(realLooper).post(runnable); 635 Message msg; 636 do { 637 msg = getNextExecutableMessage(); 638 if (msg == null) { 639 throw new IllegalStateException("Runnable is not in the queue"); 640 } 641 msg.getTarget().dispatchMessage(msg); 642 triggerIdleHandlersIfNeeded(msg); 643 } while (msg.getCallback() != runnable); 644 } 645 } 646 647 private class PostAsyncAndIdleToRunnable extends ControlRunnable { 648 private final Runnable runnable; 649 private final Handler handler; 650 PostAsyncAndIdleToRunnable(Runnable runnable)651 PostAsyncAndIdleToRunnable(Runnable runnable) { 652 this.runnable = runnable; 653 this.handler = createAsyncHandler(realLooper); 654 } 655 656 @Override doRun()657 public void doRun() { 658 handler.postAtFrontOfQueue(runnable); 659 Message msg; 660 do { 661 msg = getNextExecutableMessage(); 662 if (msg == null) { 663 throw new IllegalStateException("Runnable is not in the queue"); 664 } 665 msg.getTarget().dispatchMessage(msg); 666 667 } while (msg.getCallback() != runnable); 668 } 669 } 670 671 /** 672 * Executes the given runnable on the loopers thread, and waits for it to complete. 673 * 674 * @throws IllegalStateException if Looper is quitting or has stopped due to uncaught exception 675 */ executeOnLooper(ControlRunnable runnable)676 private void executeOnLooper(ControlRunnable runnable) throws IllegalStateException { 677 checkState(!shadowQueue().isQuitting(), "Looper is quitting"); 678 if (Thread.currentThread() == realLooper.getThread()) { 679 if (runnable instanceof UnPauseRunnable) { 680 // Need to trigger the unpause action in PausedLooperExecutor 681 looperExecutor.execute(runnable); 682 } else { 683 try { 684 runnable.run(); 685 } catch (ControlException e) { 686 e.rethrowCause(); 687 } 688 } 689 } else { 690 if (looperMode() == LooperMode.Mode.PAUSED && realLooper.equals(Looper.getMainLooper())) { 691 throw new UnsupportedOperationException( 692 "main looper can only be controlled from main thread"); 693 } 694 looperExecutor.execute(runnable); 695 runnable.waitTillComplete(); 696 // throw immediately if looper died while executing tasks 697 shadowQueue().checkQueueState(); 698 } 699 } 700 701 /** 702 * A runnable that will block normal looper execution of messages aka will 'pause' the looper. 703 * 704 * <p>Message execution can be triggered by posting messages to this runnable. 705 */ 706 private class PausedLooperExecutor extends ControlRunnable implements Executor { 707 708 private final LinkedBlockingQueue<Runnable> executionQueue = new LinkedBlockingQueue<>(); 709 710 @Override execute(Runnable runnable)711 public void execute(Runnable runnable) { 712 shadowQueue().checkQueueState(); 713 executionQueue.add(runnable); 714 } 715 716 @Override run()717 public void run() { 718 setLooperExecutor(this); 719 isPaused = true; 720 runLatch.countDown(); 721 while (isPaused) { 722 try { 723 Runnable runnable = executionQueue.take(); 724 runnable.run(); 725 } catch (InterruptedException e) { 726 // ignored 727 } 728 } 729 } 730 731 @Override doRun()732 protected void doRun() throws RuntimeException { 733 throw new UnsupportedOperationException(); 734 } 735 } 736 737 private class UnPauseRunnable extends ControlRunnable { 738 @Override doRun()739 public void doRun() { 740 setLooperExecutor(new HandlerExecutor(realLooper)); 741 isPaused = false; 742 } 743 } 744 createAsyncHandler(Looper looper)745 static Handler createAsyncHandler(Looper looper) { 746 if (RuntimeEnvironment.getApiLevel() >= 28) { 747 // createAsync is only available in API 28+ 748 return Handler.createAsync(looper); 749 } else { 750 return new Handler(looper, null, true); 751 } 752 } 753 754 private static class HandlerExecutor implements Executor { 755 private final Handler handler; 756 HandlerExecutor(Looper looper)757 private HandlerExecutor(Looper looper) { 758 // always post async messages so ControlRunnables get processed even if Looper is blocked on a 759 // sync barrier 760 this.handler = createAsyncHandler(looper); 761 } 762 763 @Override execute(Runnable runnable)764 public void execute(Runnable runnable) { 765 if (!handler.post(runnable)) { 766 throw new IllegalStateException( 767 String.format("post to %s failed. Is handler thread dead?", handler)); 768 } 769 } 770 } 771 772 @ForType(Looper.class) 773 interface LooperReflector { 774 775 @Static 776 @Direct prepareMainLooper()777 void prepareMainLooper(); 778 779 @Direct quit()780 void quit(); 781 782 @Direct quitSafely()783 void quitSafely(); 784 785 @Direct loop()786 void loop(); 787 788 @Accessor("mThread") setThread(Thread thread)789 void setThread(Thread thread); 790 791 @Accessor("sThreadLocal") getThreadLocal()792 ThreadLocal<Looper> getThreadLocal(); 793 } 794 } 795