Skip to content

Commit be462cd

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Make TestLooperManagerCompat call TestLooperManager APIs when available.
PiperOrigin-RevId: 712978736
1 parent 1a2b4d4 commit be462cd

File tree

2 files changed

+99
-28
lines changed

2 files changed

+99
-28
lines changed

espresso/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ The following artifacts were released:
1717
**Bug Fixes**
1818

1919
* Fix deadlock in espresso in Robolectric INSTRUMENTATION_TEST + paused looper.
20-
* Refactor espresso's MessageQueue access into a TestLooperManagerCompat class
20+
* Refactor espresso's MessageQueue access into a TestLooperManagerCompat class,
21+
and use new TestLooperManager APIs when available.
2122

2223
**New Features**
2324

espresso/core/java/androidx/test/espresso/base/TestLooperManagerCompat.java

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package androidx.test.espresso.base;
22

3+
import static androidx.test.internal.util.Checks.checkNotNull;
4+
5+
import android.os.Build.VERSION;
6+
import android.os.Build.VERSION_CODES;
37
import android.os.Looper;
48
import android.os.Message;
59
import android.os.MessageQueue;
10+
import android.os.TestLooperManager;
611
import androidx.annotation.Nullable;
12+
import androidx.test.platform.app.InstrumentationRegistry;
713
import java.lang.reflect.Field;
814
import java.lang.reflect.Method;
915

@@ -13,81 +19,145 @@
1319
*
1420
* <p>Unlike the real TestLooperManager this only supports being used from the Looper's thread.
1521
*/
16-
class TestLooperManagerCompat {
22+
@SuppressWarnings("NonFinalStaticField")
23+
final class TestLooperManagerCompat {
1724

18-
private static final Method messageQueueNextMethod;
19-
private static final Field messageQueueHeadField;
20-
private static final Method recycleUncheckedMethod;
25+
private static Method messageQueueNextMethod;
26+
private static Field messageQueueHeadField;
27+
private static Method recycleUncheckedMethod;
28+
private static Method peekWhenMethod;
29+
private static Method blockedOnBarrierMethod;
30+
31+
private static boolean initTestLooperManager() {
32+
// TODO(b/112000181): update this check and remove reflection when compiling against Baklava
33+
if (VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) {
34+
try {
35+
peekWhenMethod = TestLooperManager.class.getDeclaredMethod("peekWhen");
36+
blockedOnBarrierMethod =
37+
TestLooperManager.class.getDeclaredMethod("isBlockedOnSyncBarrier");
38+
return true;
39+
} catch (ReflectiveOperationException e) {
40+
// fall through
41+
}
42+
}
43+
return false;
44+
}
2145

2246
static {
2347
try {
24-
messageQueueNextMethod = MessageQueue.class.getDeclaredMethod("next");
25-
messageQueueNextMethod.setAccessible(true);
26-
messageQueueHeadField = MessageQueue.class.getDeclaredField("mMessages");
27-
messageQueueHeadField.setAccessible(true);
28-
recycleUncheckedMethod = Message.class.getDeclaredMethod("recycleUnchecked");
29-
recycleUncheckedMethod.setAccessible(true);
48+
if (!initTestLooperManager()) {
49+
messageQueueNextMethod = MessageQueue.class.getDeclaredMethod("next");
50+
messageQueueNextMethod.setAccessible(true);
51+
messageQueueHeadField = MessageQueue.class.getDeclaredField("mMessages");
52+
messageQueueHeadField.setAccessible(true);
53+
recycleUncheckedMethod = Message.class.getDeclaredMethod("recycleUnchecked");
54+
recycleUncheckedMethod.setAccessible(true);
55+
}
3056
} catch (ReflectiveOperationException e) {
3157
throw new RuntimeException(e);
3258
}
3359
}
3460

3561
private final MessageQueue queue;
3662

63+
// the TestLooperManager to defer to. Will only be non-null if running
64+
// on an Android API level that supports it
65+
private final TestLooperManager delegate;
66+
3767
private TestLooperManagerCompat(MessageQueue queue) {
3868
this.queue = queue;
69+
this.delegate = null;
70+
}
71+
72+
private TestLooperManagerCompat(TestLooperManager testLooperManager) {
73+
this.queue = null;
74+
this.delegate = testLooperManager;
3975
}
4076

4177
static TestLooperManagerCompat acquire(Looper looper) {
42-
return new TestLooperManagerCompat(looper.getQueue());
78+
if (peekWhenMethod != null) {
79+
// running on a newer Android version that has the supported TestLooperManagerCompat changes
80+
TestLooperManager testLooperManager =
81+
InstrumentationRegistry.getInstrumentation().acquireLooperManager(looper);
82+
return new TestLooperManagerCompat(testLooperManager);
83+
} else {
84+
return new TestLooperManagerCompat(looper.getQueue());
85+
}
4386
}
4487

4588
@Nullable
4689
Long peekWhen() {
47-
Message msg = legacyPeek();
48-
if (msg != null && msg.getTarget() == null) {
49-
return null;
90+
try {
91+
if (delegate != null) {
92+
return (Long) peekWhenMethod.invoke(delegate);
93+
} else {
94+
Message msg = legacyPeek();
95+
if (msg != null && msg.getTarget() == null) {
96+
return null;
97+
}
98+
return msg == null ? null : msg.getWhen();
99+
}
100+
} catch (ReflectiveOperationException e) {
101+
throw new RuntimeException(e);
50102
}
51-
return msg == null ? null : msg.getWhen();
52103
}
53104

54105
@Nullable
55-
private Message legacyPeek() {
106+
private Message legacyPeek() throws IllegalAccessException {
107+
checkNotNull(queue);
56108
// the legacy MessageQueue implementation synchronizes on itself,
57109
// so this uses the same lock
58110
synchronized (queue) {
59-
try {
60-
return (Message) messageQueueHeadField.get(queue);
61-
} catch (IllegalAccessException e) {
62-
throw new RuntimeException(e);
63-
}
111+
return (Message) messageQueueHeadField.get(queue);
64112
}
65113
}
66114

67115
void execute(Message message) {
68-
message.getTarget().dispatchMessage(message);
116+
if (delegate != null) {
117+
delegate.execute(message);
118+
} else {
119+
message.getTarget().dispatchMessage(message);
120+
}
69121
}
70122

71123
void release() {
72-
// ignore for now
124+
if (delegate != null) {
125+
delegate.release();
126+
}
73127
}
74128

75129
boolean isBlockedOnSyncBarrier() {
76-
Message msg = legacyPeek();
77-
return msg != null && msg.getTarget() == null;
130+
try {
131+
if (delegate != null) {
132+
return (boolean) blockedOnBarrierMethod.invoke(delegate);
133+
} else {
134+
Message msg = legacyPeek();
135+
return msg != null && msg.getTarget() == null;
136+
}
137+
} catch (ReflectiveOperationException e) {
138+
throw new RuntimeException(e);
139+
}
78140
}
79141

80142
Message next() {
81143
try {
82-
return (Message) messageQueueNextMethod.invoke(queue);
144+
if (delegate != null) {
145+
return delegate.next();
146+
} else {
147+
return (Message) messageQueueNextMethod.invoke(queue);
148+
}
83149
} catch (ReflectiveOperationException e) {
84150
throw new RuntimeException(e);
85151
}
86152
}
87153

88154
void recycle(Message m) {
89155
try {
90-
recycleUncheckedMethod.invoke(m);
156+
if (delegate != null) {
157+
delegate.recycle(m);
158+
} else {
159+
recycleUncheckedMethod.invoke(m);
160+
}
91161
} catch (ReflectiveOperationException e) {
92162
throw new RuntimeException(e);
93163
}

0 commit comments

Comments
 (0)