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 ;
3227
3328/** Isolates the nasty details of touching the message queue. */
3429final class Interrogator {
3530
3631 private static final String TAG = "Interrogator" ;
37- private static final Method messageQueueNextMethod ;
38- private static final Field messageQueueHeadField ;
39- private static final Method recycleUncheckedMethod ;
4032
4133 private static final int LOOKAHEAD_MILLIS = 15 ;
4234 private static final ThreadLocal <Boolean > interrogating =
@@ -47,30 +39,7 @@ public Boolean initialValue() {
4739 }
4840 };
4941
50- static {
51- try {
52- // TODO(b/112000181): remove the hidden api access here
53- messageQueueNextMethod = MessageQueue .class .getDeclaredMethod ("next" );
54- messageQueueNextMethod .setAccessible (true );
5542
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- }
7443
7544 /** Informed of the state of the queue and controls whether to continue interrogation or quit. */
7645 interface QueueInterrogationHandler <R > {
@@ -133,17 +102,18 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
133102 checkSanity ();
134103 interrogating .set (Boolean .TRUE );
135104 boolean stillInterested = true ;
136- MessageQueue q = Looper .myQueue ();
105+ TestLooperManagerCompat testLooperManager = TestLooperManagerCompat .acquire (Looper .myLooper ());
106+
137107 // We may have an identity when we're called - we want to restore it at the end of the fn.
138108 final long entryIdentity = Binder .clearCallingIdentity ();
139109 try {
140110 // this identity should not get changed by dispatching the loop until the observer is happy.
141111 final long threadIdentity = Binder .clearCallingIdentity ();
142112 while (stillInterested ) {
143113 // run until the observer is no longer interested.
144- stillInterested = interrogateQueueState (q , handler );
114+ stillInterested = interrogateQueueState (testLooperManager , handler );
145115 if (stillInterested ) {
146- Message m = getNextMessage ();
116+ Message m = testLooperManager . next ();
147117
148118 // the observer cannot stop us from dispatching this message - but we need to let it know
149119 // that we're about to dispatch.
@@ -153,7 +123,7 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
153123 }
154124 stillInterested = handler .beforeTaskDispatch ();
155125 handler .setMessage (m );
156- m . getTarget (). dispatchMessage (m );
126+ testLooperManager . execute (m );
157127
158128 // ensure looper invariants
159129 final long newIdentity = Binder .clearCallingIdentity ();
@@ -172,48 +142,17 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
172142 + " what="
173143 + m .what );
174144 }
175- recycle (m );
145+ testLooperManager . recycle (m );
176146 }
177147 }
178148 } finally {
179149 Binder .restoreCallingIdentity (entryIdentity );
180150 interrogating .set (Boolean .FALSE );
151+ testLooperManager .release ();
181152 }
182153 return handler .get ();
183154 }
184155
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-
217156 /**
218157 * Allows caller to see if the message queue is empty, has a task due soon / long, or has a
219158 * barrier.
@@ -228,36 +167,40 @@ private static Message getNextMessage() {
228167 * queueEmpty(), taskDueSoon(), taskDueLong() or barrierUp(). once and only once.
229168 * @return the result of handler.get()
230169 */
231- static <R > R peekAtQueueState (MessageQueue q , QueueInterrogationHandler <R > handler ) {
232- checkNotNull (q );
170+ static <R > R peekAtQueueState (
171+ TestLooperManagerCompat testLooperManager , QueueInterrogationHandler <R > handler ) {
172+ checkNotNull (testLooperManager );
233173 checkNotNull (handler );
234174 checkState (
235- !interrogateQueueState (q , handler ),
175+ !interrogateQueueState (testLooperManager , handler ),
236176 "It is expected that %s would stop interrogation after a single peak at the queue." ,
237177 handler );
238178 return handler .get ();
239179 }
240180
181+ static <R > R peekAtQueueState (Looper looper , QueueInterrogationHandler <R > handler ) {
182+ TestLooperManagerCompat testLooperManager = TestLooperManagerCompat .acquire (looper );
183+ try {
184+ return peekAtQueueState (testLooperManager , handler );
185+ } finally {
186+ testLooperManager .release ();
187+ }
188+ }
189+
241190 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.
191+ TestLooperManagerCompat testLooperManager , QueueInterrogationHandler <?> handler ) {
192+
193+ Long headWhen = testLooperManager .peekWhen ();
194+ if (null == headWhen ) {
195+ if (testLooperManager .isBlockedOnSyncBarrier ()) {
255196 if (Log .isLoggable (TAG , Log .DEBUG )) {
256197 Log .d (TAG , "barrier is up" );
257198 }
258199 return handler .barrierUp ();
259200 }
260- long headWhen = head .getWhen ();
201+ return handler .queueEmpty ();
202+ }
203+
261204 long nowFuz = SystemClock .uptimeMillis () + LOOKAHEAD_MILLIS ;
262205 if (Log .isLoggable (TAG , Log .DEBUG )) {
263206 Log .d (
@@ -268,7 +211,6 @@ private static boolean interrogateQueueState(
268211 return handler .taskDueSoon ();
269212 }
270213 return handler .taskDueLong ();
271- }
272214 }
273215
274216 private static void checkSanity () {
0 commit comments