• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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