Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
      4 import static android.os.Build.VERSION_CODES.LOLLIPOP;
      5 import static org.robolectric.RuntimeEnvironment.getApiLevel;
      6 import static org.robolectric.Shadows.shadowOf;
      7 import static org.robolectric.shadow.api.Shadow.directlyOn;
      8 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
      9 import static org.robolectric.util.ReflectionHelpers.callInstanceMethod;
     10 import static org.robolectric.util.ReflectionHelpers.getField;
     11 import static org.robolectric.util.ReflectionHelpers.setField;
     12 
     13 import android.os.Handler;
     14 import android.os.Message;
     15 import android.os.MessageQueue;
     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.util.Scheduler;
     21 
     22 /**
     23  * Robolectric puts {@link android.os.Message}s into the scheduler queue instead of sending
     24  * them to be handled on a separate thread. {@link android.os.Message}s that are scheduled to
     25  * be dispatched can be triggered by calling {@link ShadowLooper#idleMainLooper}.
     26  *
     27  * @see ShadowLooper
     28  */
     29 @Implements(MessageQueue.class)
     30 public class ShadowMessageQueue {
     31 
     32   @RealObject
     33   private MessageQueue realQueue;
     34 
     35   private Scheduler scheduler;
     36 
     37   // Stub out the native peer - scheduling
     38   // is handled by the Scheduler class which is user-driven
     39   // rather than automatic.
     40   @HiddenApi
     41   @Implementation
     42   public static Number nativeInit() {
     43     return 1;
     44   }
     45 
     46   @HiddenApi
     47   @Implementation(maxSdk = KITKAT_WATCH)
     48   public static void nativeDestroy(int ptr) {
     49     nativeDestroy((long) ptr);
     50   }
     51 
     52   @Implementation(minSdk = LOLLIPOP)
     53   public static void nativeDestroy(long ptr) {
     54   }
     55 
     56   @HiddenApi
     57   @Implementation(maxSdk = KITKAT_WATCH)
     58   public static void nativePollOnce(int ptr, int timeoutMillis) {
     59     nativePollOnce((long) ptr, timeoutMillis);
     60   }
     61 
     62   @Implementation(minSdk = LOLLIPOP)
     63   public static void nativePollOnce(long ptr, int timeoutMillis) {
     64     throw new AssertionError("Should not be called");
     65   }
     66 
     67   @HiddenApi
     68   @Implementation(maxSdk = KITKAT_WATCH)
     69   public static void nativeWake(int ptr) {
     70     nativeWake((long) ptr);
     71   }
     72 
     73   @Implementation(minSdk = LOLLIPOP)
     74   public static void nativeWake(long ptr) {
     75     throw new AssertionError("Should not be called");
     76   }
     77 
     78   @HiddenApi
     79   @Implementation(maxSdk = KITKAT_WATCH)
     80   public static boolean nativeIsIdling(int ptr) {
     81     return nativeIsIdling((long) ptr);
     82   }
     83 
     84   @Implementation(minSdk = LOLLIPOP)
     85   public static boolean nativeIsIdling(long ptr) {
     86     return false;
     87   }
     88 
     89   public Scheduler getScheduler() {
     90     return scheduler;
     91   }
     92 
     93   public void setScheduler(Scheduler scheduler) {
     94     this.scheduler = scheduler;
     95   }
     96 
     97   public Message getHead() {
     98     return getField(realQueue, "mMessages");
     99   }
    100 
    101   public void setHead(Message msg) {
    102     setField(realQueue, "mMessages", msg);
    103   }
    104 
    105   public void reset() {
    106     setHead(null);
    107   }
    108 
    109   @Implementation
    110   @SuppressWarnings("SynchronizeOnNonFinalField")
    111   public boolean enqueueMessage(final Message msg, long when) {
    112     final boolean retval = directlyOn(realQueue, MessageQueue.class, "enqueueMessage", from(Message.class, msg), from(long.class, when));
    113     if (retval) {
    114       final Runnable callback = new Runnable() {
    115         @Override
    116         public void run() {
    117           synchronized (realQueue) {
    118             Message m = getHead();
    119             if (m == null) {
    120               return;
    121             }
    122 
    123             Message n = shadowOf(m).getNext();
    124             if (m == msg) {
    125               setHead(n);
    126             } else {
    127               while (n != null) {
    128                 if (n == msg) {
    129                   n = shadowOf(n).getNext();
    130                   shadowOf(m).setNext(n);
    131                   break;
    132                 }
    133                 m = n;
    134                 n = shadowOf(m).getNext();
    135               }
    136             }
    137           }
    138           dispatchMessage(msg);
    139         }
    140       };
    141       shadowOf(msg).setScheduledRunnable(callback);
    142       if (when == 0) {
    143         scheduler.postAtFrontOfQueue(callback);
    144       } else {
    145         scheduler.postDelayed(callback, when - scheduler.getCurrentTime());
    146       }
    147     }
    148     return retval;
    149   }
    150 
    151   @HiddenApi
    152   @Implementation
    153   public void removeSyncBarrier(int token) {
    154   }
    155 
    156   private static void dispatchMessage(Message msg) {
    157     final Handler target = msg.getTarget();
    158 
    159     shadowOf(msg).setNext(null);
    160     // If target is null it means the message has been removed
    161     // from the queue prior to being dispatched by the scheduler.
    162     if (target != null) {
    163       callInstanceMethod(msg, "markInUse");
    164       target.dispatchMessage(msg);
    165 
    166       if (getApiLevel() >= LOLLIPOP) {
    167         callInstanceMethod(msg, "recycleUnchecked");
    168       } else {
    169         callInstanceMethod(msg, "recycle");
    170       }
    171     }
    172   }
    173 }
    174