1616
1717package androidx .test .espresso .base ;
1818
19- import static androidx .test .espresso .util .Throwables .throwIfUnchecked ;
2019import static androidx .test .internal .util .Checks .checkNotNull ;
2120import static androidx .test .internal .util .Checks .checkState ;
2221
2322import android .os .Binder ;
2423import android .os .Looper ;
2524import android .os .Message ;
26- import android .os .MessageQueue ;
2725import android .os .SystemClock ;
2826import android .util .Log ;
29- import java .lang .reflect .Field ;
30- import java .lang .reflect .InvocationTargetException ;
31- import java .lang .reflect .Method ;
27+ import androidx .annotation .VisibleForTesting ;
3228
3329/** Isolates the nasty details of touching the message queue. */
3430final class Interrogator {
3531
3632 private static final String TAG = "Interrogator" ;
37- private static final Method messageQueueNextMethod ;
38- private static final Field messageQueueHeadField ;
39- private static final Method recycleUncheckedMethod ;
4033
41- private static final int LOOKAHEAD_MILLIS = 15 ;
34+ @ VisibleForTesting static final int LOOKAHEAD_MILLIS = 15 ;
4235 private static final ThreadLocal <Boolean > interrogating =
4336 new ThreadLocal <Boolean >() {
4437 @ Override
@@ -47,30 +40,7 @@ public Boolean initialValue() {
4740 }
4841 };
4942
50- static {
51- try {
52- // TODO(b/112000181): remove the hidden api access here
53- messageQueueNextMethod = MessageQueue .class .getDeclaredMethod ("next" );
54- messageQueueNextMethod .setAccessible (true );
5543
56- messageQueueHeadField = MessageQueue .class .getDeclaredField ("mMessages" );
57- messageQueueHeadField .setAccessible (true );
58- } catch (IllegalArgumentException
59- | NoSuchFieldException
60- | SecurityException
61- | NoSuchMethodException e ) {
62- Log .e (TAG , "Could not initialize interrogator!" , e );
63- throw new RuntimeException ("Could not initialize interrogator!" , e );
64- }
65-
66- Method recycleUnchecked = null ;
67- try {
68- recycleUnchecked = Message .class .getDeclaredMethod ("recycleUnchecked" );
69- recycleUnchecked .setAccessible (true );
70- } catch (NoSuchMethodException expectedOnLowerApiLevels ) {
71- }
72- recycleUncheckedMethod = recycleUnchecked ;
73- }
7444
7545 /** Informed of the state of the queue and controls whether to continue interrogation or quit. */
7646 interface QueueInterrogationHandler <R > {
@@ -133,17 +103,18 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
133103 checkSanity ();
134104 interrogating .set (Boolean .TRUE );
135105 boolean stillInterested = true ;
136- MessageQueue q = Looper .myQueue ();
106+ TestLooperManagerCompat testLooperManager = TestLooperManagerCompat .acquire (Looper .myLooper ());
107+
137108 // We may have an identity when we're called - we want to restore it at the end of the fn.
138109 final long entryIdentity = Binder .clearCallingIdentity ();
139110 try {
140111 // this identity should not get changed by dispatching the loop until the observer is happy.
141112 final long threadIdentity = Binder .clearCallingIdentity ();
142113 while (stillInterested ) {
143114 // run until the observer is no longer interested.
144- stillInterested = interrogateQueueState (q , handler );
115+ stillInterested = interrogateQueueState (testLooperManager , handler );
145116 if (stillInterested ) {
146- Message m = getNextMessage ();
117+ Message m = testLooperManager . next ();
147118
148119 // the observer cannot stop us from dispatching this message - but we need to let it know
149120 // that we're about to dispatch.
@@ -153,7 +124,7 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
153124 }
154125 stillInterested = handler .beforeTaskDispatch ();
155126 handler .setMessage (m );
156- m . getTarget (). dispatchMessage (m );
127+ testLooperManager . execute (m );
157128
158129 // ensure looper invariants
159130 final long newIdentity = Binder .clearCallingIdentity ();
@@ -172,48 +143,17 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
172143 + " what="
173144 + m .what );
174145 }
175- recycle (m );
146+ testLooperManager . recycle (m );
176147 }
177148 }
178149 } finally {
179150 Binder .restoreCallingIdentity (entryIdentity );
180151 interrogating .set (Boolean .FALSE );
152+ testLooperManager .release ();
181153 }
182154 return handler .get ();
183155 }
184156
185- private static void recycle (Message m ) {
186- if (recycleUncheckedMethod != null ) {
187- try {
188- recycleUncheckedMethod .invoke (m );
189- } catch (IllegalAccessException | IllegalArgumentException | SecurityException e ) {
190- throwIfUnchecked (e );
191- throw new RuntimeException (e );
192- } catch (InvocationTargetException ite ) {
193- if (ite .getCause () != null ) {
194- throwIfUnchecked (ite .getCause ());
195- throw new RuntimeException (ite .getCause ());
196- } else {
197- throw new RuntimeException (ite );
198- }
199- }
200- } else {
201- m .recycle ();
202- }
203- }
204-
205- private static Message getNextMessage () {
206- try {
207- return (Message ) messageQueueNextMethod .invoke (Looper .myQueue ());
208- } catch (IllegalAccessException
209- | IllegalArgumentException
210- | InvocationTargetException
211- | SecurityException e ) {
212- throwIfUnchecked (e );
213- throw new RuntimeException (e );
214- }
215- }
216-
217157 /**
218158 * Allows caller to see if the message queue is empty, has a task due soon / long, or has a
219159 * barrier.
@@ -228,36 +168,40 @@ private static Message getNextMessage() {
228168 * queueEmpty(), taskDueSoon(), taskDueLong() or barrierUp(). once and only once.
229169 * @return the result of handler.get()
230170 */
231- static <R > R peekAtQueueState (MessageQueue q , QueueInterrogationHandler <R > handler ) {
232- checkNotNull (q );
171+ static <R > R peekAtQueueState (
172+ TestLooperManagerCompat testLooperManager , QueueInterrogationHandler <R > handler ) {
173+ checkNotNull (testLooperManager );
233174 checkNotNull (handler );
234175 checkState (
235- !interrogateQueueState (q , handler ),
176+ !interrogateQueueState (testLooperManager , handler ),
236177 "It is expected that %s would stop interrogation after a single peak at the queue." ,
237178 handler );
238179 return handler .get ();
239180 }
240181
182+ static <R > R peekAtQueueState (Looper looper , QueueInterrogationHandler <R > handler ) {
183+ TestLooperManagerCompat testLooperManager = TestLooperManagerCompat .acquire (looper );
184+ try {
185+ return peekAtQueueState (testLooperManager , handler );
186+ } finally {
187+ testLooperManager .release ();
188+ }
189+ }
190+
241191 private static boolean interrogateQueueState (
242- MessageQueue q , QueueInterrogationHandler <?> handler ) {
243- synchronized (q ) {
244- final Message head ;
245- try {
246- head = (Message ) messageQueueHeadField .get (q );
247- } catch (IllegalAccessException e ) {
248- throw new RuntimeException (e );
249- }
250- if (null == head ) {
251- // no messages pending - AT ALL!
252- return handler .queueEmpty ();
253- } else if (null == head .getTarget ()) {
254- // null target is a sync barrier token.
255- if (Log .isLoggable (TAG , Log .DEBUG )) {
256- Log .d (TAG , "barrier is up" );
192+ TestLooperManagerCompat testLooperManager , QueueInterrogationHandler <?> handler ) {
193+ synchronized (testLooperManager .getQueue ()) {
194+ Long headWhen = testLooperManager .peekWhen ();
195+ if (headWhen == null ) {
196+ if (testLooperManager .isBlockedOnSyncBarrier ()) {
197+ if (Log .isLoggable (TAG , Log .DEBUG )) {
198+ Log .d (TAG , "barrier is up" );
199+ }
200+ return handler .barrierUp ();
257201 }
258- return handler .barrierUp ();
202+ return handler .queueEmpty ();
259203 }
260- long headWhen = head . getWhen ();
204+
261205 long nowFuz = SystemClock .uptimeMillis () + LOOKAHEAD_MILLIS ;
262206 if (Log .isLoggable (TAG , Log .DEBUG )) {
263207 Log .d (
0 commit comments