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