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