Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
      4 import static org.robolectric.RuntimeEnvironment.isMainThread;
      5 import static org.robolectric.Shadows.shadowOf;
      6 import static org.robolectric.shadow.api.Shadow.invokeConstructor;
      7 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
      8 
      9 import android.os.Looper;
     10 import java.util.Collections;
     11 import java.util.Map;
     12 import java.util.WeakHashMap;
     13 import java.util.concurrent.TimeUnit;
     14 import org.robolectric.RoboSettings;
     15 import org.robolectric.RuntimeEnvironment;
     16 import org.robolectric.annotation.HiddenApi;
     17 import org.robolectric.annotation.Implementation;
     18 import org.robolectric.annotation.Implements;
     19 import org.robolectric.annotation.RealObject;
     20 import org.robolectric.annotation.Resetter;
     21 import org.robolectric.util.Scheduler;
     22 
     23 /**
     24  * Robolectric enqueues posted {@link Runnable}s to be run
     25  * (on this thread) later. {@code Runnable}s that are scheduled to run immediately can be
     26  * triggered by calling {@link #idle()}.
     27  *
     28  * @see ShadowMessageQueue
     29  */
     30 @Implements(Looper.class)
     31 @SuppressWarnings("SynchronizeOnNonFinalField")
     32 public class ShadowLooper {
     33   // Replaced SoftThreadLocal with a WeakHashMap, because ThreadLocal make it impossible to access their contents from other
     34   // threads, but we need to be able to access the loopers for all threads so that we can shut them down when resetThreadLoopers()
     35   // is called. This also allows us to implement the useful getLooperForThread() method.
     36   // Note that the main looper is handled differently and is not put in this hash, because we need to be able to
     37   // "switch" the thread that the main looper is associated with.
     38   private static Map<Thread, Looper> loopingLoopers = Collections.synchronizedMap(new WeakHashMap<Thread, Looper>());
     39 
     40   private static Looper mainLooper;
     41 
     42   private @RealObject Looper realObject;
     43 
     44   boolean quit;
     45 
     46   @Resetter
     47   public static synchronized void resetThreadLoopers() {
     48     // Blech. We need to keep the main looper because somebody might refer to it in a static
     49     // field. The other loopers need to be wrapped in WeakReferences so that they are not prevented from
     50     // being garbage collected.
     51     if (!isMainThread()) {
     52       throw new IllegalStateException("you should only be calling this from the main thread!");
     53     }
     54     synchronized (loopingLoopers) {
     55       for (Looper looper : loopingLoopers.values()) {
     56         synchronized (looper) {
     57           if (!shadowOf(looper).quit) {
     58             looper.quit();
     59           } else {
     60             // Reset the schedulers of all loopers. This prevents un-run tasks queued up in static
     61             // background handlers from leaking to subsequent tests.
     62             shadowOf(looper).getScheduler().reset();
     63           }
     64         }
     65       }
     66     }
     67     // Because resetStaticState() is called by ParallelUniverse on startup before prepareMainLooper() is
     68     // called, this might be null on that occasion.
     69     if (mainLooper != null) {
     70       shadowOf(mainLooper).reset();
     71     }
     72   }
     73 
     74   @Implementation
     75   public void __constructor__(boolean quitAllowed) {
     76     invokeConstructor(Looper.class, realObject, from(boolean.class, quitAllowed));
     77     if (isMainThread()) {
     78       mainLooper = realObject;
     79     } else {
     80       loopingLoopers.put(Thread.currentThread(), realObject);
     81     }
     82     resetScheduler();
     83   }
     84 
     85   @Implementation
     86   public static Looper getMainLooper() {
     87     return mainLooper;
     88   }
     89 
     90   @Implementation
     91   public static Looper myLooper() {
     92     return getLooperForThread(Thread.currentThread());
     93   }
     94 
     95   @Implementation
     96   public static void loop() {
     97     shadowOf(Looper.myLooper()).doLoop();
     98   }
     99 
    100   private void doLoop() {
    101     if (realObject != Looper.getMainLooper()) {
    102       synchronized (realObject) {
    103         while (!quit) {
    104           try {
    105             realObject.wait();
    106           } catch (InterruptedException ignore) {
    107           }
    108         }
    109       }
    110     }
    111   }
    112 
    113   @Implementation
    114   public void quit() {
    115     if (realObject == Looper.getMainLooper()) throw new RuntimeException("Main thread not allowed to quit");
    116     quitUnchecked();
    117   }
    118 
    119   @Implementation(minSdk = JELLY_BEAN_MR2)
    120   public void quitSafely() {
    121     quit();
    122   }
    123 
    124   public void quitUnchecked() {
    125     synchronized (realObject) {
    126       quit = true;
    127       realObject.notifyAll();
    128       getScheduler().reset();
    129     }
    130   }
    131 
    132   @HiddenApi @Implementation
    133   public int postSyncBarrier() {
    134     return 1;
    135   }
    136 
    137   public boolean hasQuit() {
    138     synchronized (realObject) {
    139       return quit;
    140     }
    141   }
    142 
    143   public static ShadowLooper getShadowMainLooper() {
    144     return shadowOf(Looper.getMainLooper());
    145   }
    146 
    147   public static Looper getLooperForThread(Thread thread) {
    148     return isMainThread(thread) ? mainLooper : loopingLoopers.get(thread);
    149   }
    150 
    151   public static void pauseLooper(Looper looper) {
    152     shadowOf(looper).pause();
    153   }
    154 
    155   public static void unPauseLooper(Looper looper) {
    156     shadowOf(looper).unPause();
    157   }
    158 
    159   public static void pauseMainLooper() {
    160     getShadowMainLooper().pause();
    161   }
    162 
    163   public static void unPauseMainLooper() {
    164     getShadowMainLooper().unPause();
    165   }
    166 
    167   public static void idleMainLooper() {
    168     getShadowMainLooper().idle();
    169   }
    170 
    171   /** @deprecated Use {@link #idleMainLooper(long, TimeUnit)}. */
    172   @Deprecated
    173   public static void idleMainLooper(long interval) {
    174     idleMainLooper(interval, TimeUnit.MILLISECONDS);
    175   }
    176 
    177   public static void idleMainLooper(long amount, TimeUnit unit) {
    178     getShadowMainLooper().idle(amount, unit);
    179   }
    180 
    181   public static void idleMainLooperConstantly(boolean shouldIdleConstantly) {
    182     getShadowMainLooper().idleConstantly(shouldIdleConstantly);
    183   }
    184 
    185   public static void runMainLooperOneTask() {
    186     getShadowMainLooper().runOneTask();
    187   }
    188 
    189   public static void runMainLooperToNextTask() {
    190     getShadowMainLooper().runToNextTask();
    191   }
    192 
    193   /**
    194    * Runs any immediately runnable tasks previously queued on the UI thread,
    195    * e.g. by {@link android.app.Activity#runOnUiThread(Runnable)} or {@link android.os.AsyncTask#onPostExecute(Object)}.
    196    *
    197    * **Note:** calling this method does not pause or un-pause the scheduler.
    198    *
    199    * @see #runUiThreadTasksIncludingDelayedTasks
    200    */
    201   public static void runUiThreadTasks() {
    202     getShadowMainLooper().idle();
    203   }
    204 
    205   /**
    206    * Runs all runnable tasks (pending and future) that have been queued on the UI thread. Such tasks may be queued by
    207    * e.g. {@link android.app.Activity#runOnUiThread(Runnable)} or {@link android.os.AsyncTask#onPostExecute(Object)}.
    208    *
    209    * **Note:** calling this method does not pause or un-pause the scheduler, however the clock is advanced as
    210    * future tasks are run.
    211    *
    212    * @see #runUiThreadTasks
    213    */
    214   public static void runUiThreadTasksIncludingDelayedTasks() {
    215     getShadowMainLooper().runToEndOfTasks();
    216   }
    217 
    218   /**
    219    * Causes {@link Runnable}s that have been scheduled to run immediately to actually run. Does not advance the
    220    * scheduler's clock;
    221    */
    222   public void idle() {
    223     idle(0, TimeUnit.MILLISECONDS);
    224   }
    225 
    226   /**
    227    * Causes {@link Runnable}s that have been scheduled to run within the next {@code intervalMillis} milliseconds to
    228    * run while advancing the scheduler's clock.
    229    *
    230    * @deprecated Use {@link #idle(long, TimeUnit)}.
    231    */
    232   @Deprecated
    233   public void idle(long intervalMillis) {
    234     idle(intervalMillis, TimeUnit.MILLISECONDS);
    235   }
    236 
    237   /**
    238    * Causes {@link Runnable}s that have been scheduled to run within the next specified amount of time to run while
    239    * advancing the scheduler's clock.
    240    */
    241   public void idle(long amount, TimeUnit unit) {
    242     getScheduler().advanceBy(amount, unit);
    243   }
    244 
    245   public void idleConstantly(boolean shouldIdleConstantly) {
    246     getScheduler().idleConstantly(shouldIdleConstantly);
    247   }
    248 
    249   /**
    250    * Causes all of the {@link Runnable}s that have been scheduled to run while advancing the scheduler's clock to the
    251    * start time of the last scheduled {@link Runnable}.
    252    */
    253   public void runToEndOfTasks() {
    254     getScheduler().advanceToLastPostedRunnable();
    255   }
    256 
    257   /**
    258    * Causes the next {@link Runnable}(s) that have been scheduled to run while advancing the scheduler's clock to its
    259    * start time. If more than one {@link Runnable} is scheduled to run at this time then they will all be run.
    260    */
    261   public void runToNextTask() {
    262     getScheduler().advanceToNextPostedRunnable();
    263   }
    264 
    265   /**
    266    * Causes only one of the next {@link Runnable}s that have been scheduled to run while advancing the scheduler's
    267    * clock to its start time. Only one {@link Runnable} will run even if more than one has ben scheduled to run at the
    268    * same time.
    269    */
    270   public void runOneTask() {
    271     getScheduler().runOneTask();
    272   }
    273 
    274   /**
    275    * Enqueue a task to be run later.
    276    *
    277    * @param runnable    the task to be run
    278    * @param delayMillis how many milliseconds into the (virtual) future to run it
    279    * @return true if the runnable is enqueued
    280    * @see android.os.Handler#postDelayed(Runnable,long)
    281    * @deprecated Use a {@link android.os.Handler} instance to post to a looper.
    282    */
    283   @Deprecated
    284   public boolean post(Runnable runnable, long delayMillis) {
    285     if (!quit) {
    286       getScheduler().postDelayed(runnable, delayMillis, TimeUnit.MILLISECONDS);
    287       return true;
    288     } else {
    289       return false;
    290     }
    291   }
    292 
    293   /**
    294    * Enqueue a task to be run ahead of all other delayed tasks.
    295    *
    296    * @param runnable    the task to be run
    297    * @return true if the runnable is enqueued
    298    * @see android.os.Handler#postAtFrontOfQueue(Runnable)
    299    * @deprecated Use a {@link android.os.Handler} instance to post to a looper.
    300    */
    301   @Deprecated
    302   public boolean postAtFrontOfQueue(Runnable runnable) {
    303     if (!quit) {
    304       getScheduler().postAtFrontOfQueue(runnable);
    305       return true;
    306     } else {
    307       return false;
    308     }
    309   }
    310 
    311   public void pause() {
    312     getScheduler().pause();
    313   }
    314 
    315   public void unPause() {
    316     getScheduler().unPause();
    317   }
    318 
    319   public boolean isPaused() {
    320     return getScheduler().isPaused();
    321   }
    322 
    323   public boolean setPaused(boolean shouldPause) {
    324     boolean wasPaused = isPaused();
    325     if (shouldPause) {
    326       pause();
    327     } else {
    328       unPause();
    329     }
    330     return wasPaused;
    331   }
    332 
    333   public void resetScheduler() {
    334     ShadowMessageQueue sQueue = shadowOf(realObject.getQueue());
    335     if (realObject == Looper.getMainLooper() || RoboSettings.isUseGlobalScheduler()) {
    336       sQueue.setScheduler(RuntimeEnvironment.getMasterScheduler());
    337     } else {
    338       sQueue.setScheduler(new Scheduler());
    339     }
    340   }
    341 
    342   /**
    343    * Causes all enqueued tasks to be discarded, and pause state to be reset
    344    */
    345   public void reset() {
    346     shadowOf(realObject.getQueue()).reset();
    347     resetScheduler();
    348 
    349     quit = false;
    350   }
    351 
    352   /**
    353    * Returns the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued tasks.
    354    * This scheduler is managed by the Looper's associated queue.
    355    *
    356    * @return the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued tasks.
    357    */
    358   public Scheduler getScheduler() {
    359     return shadowOf(realObject.getQueue()).getScheduler();
    360   }
    361 
    362   public void runPaused(Runnable r) {
    363     boolean wasPaused = setPaused(true);
    364     try {
    365       r.run();
    366     } finally {
    367       if (!wasPaused) unPause();
    368     }
    369   }
    370 }
    371