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