@@ -32,10 +32,18 @@ import androidx.test.filters.FlakyTest
32
32
import androidx.test.filters.LargeTest
33
33
import androidx.test.platform.app.InstrumentationRegistry
34
34
import androidx.test.uiautomator.UiDevice
35
+ import java.util.concurrent.atomic.AtomicInteger
36
+ import kotlin.coroutines.ContinuationInterceptor
37
+ import kotlin.coroutines.CoroutineContext
38
+ import kotlin.test.fail
35
39
import kotlinx.coroutines.CompletableDeferred
40
+ import kotlinx.coroutines.CoroutineDispatcher
41
+ import kotlinx.coroutines.CoroutineStart
36
42
import kotlinx.coroutines.Dispatchers
43
+ import kotlinx.coroutines.Runnable
37
44
import kotlinx.coroutines.launch
38
45
import kotlinx.coroutines.runBlocking
46
+ import kotlinx.coroutines.withContext
39
47
import kotlinx.coroutines.withTimeoutOrNull
40
48
import org.junit.Assert.assertEquals
41
49
import org.junit.Assert.assertNotEquals
@@ -174,4 +182,60 @@ class AndroidUiDispatcherTest {
174
182
}
175
183
)
176
184
}
185
+
186
+ /* *
187
+ * Test that an AndroidUiDispatcher can be wrapped by another ContinuationInterceptor
188
+ * without breaking the MonotonicFrameClock's ability to coordinate with its
189
+ * original dispatcher.
190
+ *
191
+ * Construct a situation where the Choreographer contains three frame callbacks:
192
+ * 1) checkpoint 1
193
+ * 2) the AndroidUiDispatcher awaiting-frame callback
194
+ * 3) checkpoint 2
195
+ * Confirm that a call to withFrameNanos made *after* these three frame callbacks
196
+ * are enqueued runs *before* checkpoint 2, indicating that it ran with the original
197
+ * dispatcher's awaiting-frame callback, even though we wrapped the dispatcher.
198
+ */
199
+ @Test
200
+ fun wrappedDispatcherPostsToDispatcherFrameClock () = runBlocking(Dispatchers .Main ) {
201
+ val uiDispatcherContext = AndroidUiDispatcher .Main
202
+ val uiDispatcher = uiDispatcherContext[ContinuationInterceptor ] as CoroutineDispatcher
203
+ val wrapperDispatcher = object : CoroutineDispatcher () {
204
+ override fun dispatch (context : CoroutineContext , block : Runnable ) {
205
+ uiDispatcher.dispatch(context, block)
206
+ }
207
+ }
208
+
209
+ val choreographer = Choreographer .getInstance()
210
+
211
+ val expectCount = AtomicInteger (1 )
212
+ fun expect (value : Int ) {
213
+ while (true ) {
214
+ val old = expectCount.get()
215
+ if (old != value) fail(" expected sequence $old but encountered $value " )
216
+ if (expectCount.compareAndSet(value, value + 1 )) break
217
+ }
218
+ }
219
+
220
+ choreographer.postFrameCallback {
221
+ expect(1 )
222
+ }
223
+
224
+ launch(uiDispatcherContext, start = CoroutineStart .UNDISPATCHED ) {
225
+ withFrameNanos {
226
+ expect(2 )
227
+ }
228
+ }
229
+
230
+ choreographer.postFrameCallback {
231
+ expect(4 )
232
+ }
233
+
234
+ withContext(uiDispatcherContext + wrapperDispatcher) {
235
+ withFrameNanos {
236
+ expect(3 )
237
+ }
238
+ expect(5 )
239
+ }
240
+ }
177
241
}
0 commit comments