• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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