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