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