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