16
16
17
17
package androidx .test .espresso .base ;
18
18
19
- import static androidx .test .espresso .util .Throwables .throwIfUnchecked ;
20
19
import static androidx .test .internal .util .Checks .checkNotNull ;
21
20
import static androidx .test .internal .util .Checks .checkState ;
22
21
23
22
import android .os .Binder ;
24
23
import android .os .Looper ;
25
24
import android .os .Message ;
26
- import android .os .MessageQueue ;
27
25
import android .os .SystemClock ;
28
26
import 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 ;
32
28
33
29
/** Isolates the nasty details of touching the message queue. */
34
30
final class Interrogator {
35
31
36
32
private static final String TAG = "Interrogator" ;
37
- private static final Method messageQueueNextMethod ;
38
- private static final Field messageQueueHeadField ;
39
- private static final Method recycleUncheckedMethod ;
40
33
41
- private static final int LOOKAHEAD_MILLIS = 15 ;
34
+ @ VisibleForTesting static final int LOOKAHEAD_MILLIS = 15 ;
42
35
private static final ThreadLocal <Boolean > interrogating =
43
36
new ThreadLocal <Boolean >() {
44
37
@ Override
@@ -47,30 +40,7 @@ public Boolean initialValue() {
47
40
}
48
41
};
49
42
50
- static {
51
- try {
52
- // TODO(b/112000181): remove the hidden api access here
53
- messageQueueNextMethod = MessageQueue .class .getDeclaredMethod ("next" );
54
- messageQueueNextMethod .setAccessible (true );
55
43
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
- }
74
44
75
45
/** Informed of the state of the queue and controls whether to continue interrogation or quit. */
76
46
interface QueueInterrogationHandler <R > {
@@ -133,17 +103,18 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
133
103
checkSanity ();
134
104
interrogating .set (Boolean .TRUE );
135
105
boolean stillInterested = true ;
136
- MessageQueue q = Looper .myQueue ();
106
+ TestLooperManagerCompat testLooperManager = TestLooperManagerCompat .acquire (Looper .myLooper ());
107
+
137
108
// We may have an identity when we're called - we want to restore it at the end of the fn.
138
109
final long entryIdentity = Binder .clearCallingIdentity ();
139
110
try {
140
111
// this identity should not get changed by dispatching the loop until the observer is happy.
141
112
final long threadIdentity = Binder .clearCallingIdentity ();
142
113
while (stillInterested ) {
143
114
// run until the observer is no longer interested.
144
- stillInterested = interrogateQueueState (q , handler );
115
+ stillInterested = interrogateQueueState (testLooperManager , handler );
145
116
if (stillInterested ) {
146
- Message m = getNextMessage ();
117
+ Message m = testLooperManager . next ();
147
118
148
119
// the observer cannot stop us from dispatching this message - but we need to let it know
149
120
// that we're about to dispatch.
@@ -153,7 +124,7 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
153
124
}
154
125
stillInterested = handler .beforeTaskDispatch ();
155
126
handler .setMessage (m );
156
- m . getTarget (). dispatchMessage (m );
127
+ testLooperManager . execute (m );
157
128
158
129
// ensure looper invariants
159
130
final long newIdentity = Binder .clearCallingIdentity ();
@@ -172,48 +143,17 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
172
143
+ " what="
173
144
+ m .what );
174
145
}
175
- recycle (m );
146
+ testLooperManager . recycle (m );
176
147
}
177
148
}
178
149
} finally {
179
150
Binder .restoreCallingIdentity (entryIdentity );
180
151
interrogating .set (Boolean .FALSE );
152
+ testLooperManager .release ();
181
153
}
182
154
return handler .get ();
183
155
}
184
156
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
-
217
157
/**
218
158
* Allows caller to see if the message queue is empty, has a task due soon / long, or has a
219
159
* barrier.
@@ -228,36 +168,40 @@ private static Message getNextMessage() {
228
168
* queueEmpty(), taskDueSoon(), taskDueLong() or barrierUp(). once and only once.
229
169
* @return the result of handler.get()
230
170
*/
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 );
233
174
checkNotNull (handler );
234
175
checkState (
235
- !interrogateQueueState (q , handler ),
176
+ !interrogateQueueState (testLooperManager , handler ),
236
177
"It is expected that %s would stop interrogation after a single peak at the queue." ,
237
178
handler );
238
179
return handler .get ();
239
180
}
240
181
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
+
241
191
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 ();
257
201
}
258
- return handler .barrierUp ();
202
+ return handler .queueEmpty ();
259
203
}
260
- long headWhen = head . getWhen ();
204
+
261
205
long nowFuz = SystemClock .uptimeMillis () + LOOKAHEAD_MILLIS ;
262
206
if (Log .isLoggable (TAG , Log .DEBUG )) {
263
207
Log .d (
0 commit comments