diff --git a/gradle.properties b/gradle.properties index 0b62d8a629..1699aaa5bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,7 +32,10 @@ jdk_toolchain_version=11 animalsniffer_version=2.0.0 # Android versions -android_version=4.1.1.4 +android_for_testing_version=4.1.1.4 +# We're using a non-default Android artifact for building. +# See https://github.com/Kotlin/kotlinx.coroutines/issues/4158#issuecomment-2758057691 +android_via_robolectric_version=9-robolectric-4799589 androidx_annotation_version=1.1.0 robolectric_version=4.9 baksmali_version=2.2.7 diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts index 2f01e03d35..25fb289fd0 100644 --- a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts +++ b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts @@ -3,7 +3,7 @@ project.configureAar() dependencies { configureAarUnpacking() - testImplementation("com.google.android:android:${version("android")}") + testImplementation("com.google.android:android:${version("android_for_testing")}") testImplementation("org.robolectric:robolectric:${version("robolectric")}") // Required by robolectric testImplementation("androidx.test:core:1.2.0") diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt index 69dd0edf86..cbb1f84770 100644 --- a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt +++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/CustomizedRobolectricTest.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.test.* import org.junit.Test import org.junit.runner.* +import android.os.Looper import org.robolectric.* import org.robolectric.annotation.* import org.robolectric.shadows.* @@ -43,7 +44,7 @@ class CustomizedRobolectricTest : TestBase() { private fun checkComponent(component: TestComponent) { - val mainLooper = ShadowLooper.getShadowMainLooper() + val mainLooper = Shadows.shadowOf(Looper.getMainLooper()) mainLooper.pause() component.launchSomething() assertFalse(component.launchCompleted) diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt index 8777ed939d..0141e9bfee 100644 --- a/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt +++ b/ui/kotlinx-coroutines-android/android-unit-tests/test/ordered/tests/FirstRobolectricTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.test.* import org.junit.Test import org.junit.runner.* +import android.os.Looper import org.robolectric.* import org.robolectric.annotation.* import org.robolectric.shadows.* @@ -32,7 +33,7 @@ open class FirstRobolectricTest { @Test fun testDelay() { val component = TestComponent() - val mainLooper = ShadowLooper.getShadowMainLooper() + val mainLooper = Shadows.shadowOf(Looper.getMainLooper()) mainLooper.pause() component.launchDelayed() mainLooper.runToNextTask() @@ -42,7 +43,7 @@ open class FirstRobolectricTest { } private fun checkComponent(component: TestComponent) { - val mainLooper = ShadowLooper.getShadowMainLooper() + val mainLooper = Shadows.shadowOf(Looper.getMainLooper()) mainLooper.pause() component.launchSomething() assertFalse(component.launchCompleted) diff --git a/ui/kotlinx-coroutines-android/build.gradle.kts b/ui/kotlinx-coroutines-android/build.gradle.kts index adbafe4571..ba05182fef 100644 --- a/ui/kotlinx-coroutines-android/build.gradle.kts +++ b/ui/kotlinx-coroutines-android/build.gradle.kts @@ -11,10 +11,10 @@ project.configureAar() dependencies { configureAarUnpacking() - compileOnly("com.google.android:android:${version("android")}") + compileOnly("org.robolectric:android-all:${version("android_via_robolectric")}") compileOnly("androidx.annotation:annotation:${version("androidx_annotation")}") - testImplementation("com.google.android:android:${version("android")}") + testImplementation("com.google.android:android:${version("android_for_testing")}") testImplementation("org.robolectric:robolectric:${version("robolectric")}") // Required by robolectric testImplementation("androidx.test:core:1.2.0") diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 2eb40d0fe3..3011d658fd 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -7,7 +7,6 @@ import android.view.* import androidx.annotation.* import kotlinx.coroutines.* import kotlinx.coroutines.internal.* -import java.lang.reflect.* import kotlin.coroutines.* /** @@ -77,27 +76,16 @@ public fun Handler.asCoroutineDispatcher(name: String? = null): HandlerDispatche private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android @VisibleForTesting -internal fun Looper.asHandler(async: Boolean): Handler { +internal fun Looper.asHandler(async: Boolean): Handler = when { // Async support was added in API 16. - if (!async || Build.VERSION.SDK_INT < 16) { - return Handler(this) - } - - if (Build.VERSION.SDK_INT >= 28) { - // TODO compile against API 28 so this can be invoked without reflection. - val factoryMethod = Handler::class.java.getDeclaredMethod("createAsync", Looper::class.java) - return factoryMethod.invoke(null, this) as Handler - } - - val constructor: Constructor - try { - constructor = Handler::class.java.getDeclaredConstructor(Looper::class.java, - Handler.Callback::class.java, Boolean::class.javaPrimitiveType) - } catch (ignored: NoSuchMethodException) { + !async || Build.VERSION.SDK_INT < 16 -> Handler(this) + Build.VERSION.SDK_INT >= 28 -> Handler.createAsync(this) + else -> try { + Handler(this, null, true) + } catch (ignored: NoSuchMethodError) { // Hidden constructor absent. Fall back to non-async constructor. - return Handler(this) + Handler(this) } - return constructor.newInstance(this, null, true) } @JvmField diff --git a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt index 89f027adbc..ac0c7e1ca0 100644 --- a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt +++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt @@ -81,7 +81,7 @@ class HandlerDispatcherTest : MainDispatcherTestBase.WithRealTimeDelay() { } private fun CoroutineScope.doTestAwaitFrame() { - ShadowChoreographer.setPostFrameCallbackDelay(100) + ShadowLegacyChoreographer.setPostFrameCallbackDelay(100) val mainLooper = Shadows.shadowOf(Looper.getMainLooper()) mainLooper.pause() launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) { @@ -96,7 +96,7 @@ class HandlerDispatcherTest : MainDispatcherTestBase.WithRealTimeDelay() { } private fun CoroutineScope.doTestAwaitWithDetectedChoreographer() { - ShadowChoreographer.setPostFrameCallbackDelay(100) + ShadowLegacyChoreographer.setPostFrameCallbackDelay(100) val mainLooper = Shadows.shadowOf(Looper.getMainLooper()) launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) { expect(1)