Merge changes from topic "testutils-lifecycle" into androidx-main

* changes:
  Move `LifecycleOwnerUtils` to `testutils-lifecycle`
  Convert `LifecycleOwnerUtils` to Kotlin
  Move `FakeLifecycleOwner` to `testutils-lifecycle`
  Add empty `testutils-lifecycle` module
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index a133dd5..2e18a39 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -58,6 +58,7 @@
         exclude group: "androidx.lifecycle", module: "lifecycle-runtime-ktx"
     })
     androidTestImplementation(project(":internal-testutils-runtime"))
+    androidTestImplementation(project(":internal-testutils-lifecycle"))
     androidTestImplementation(project(":internal-testutils-appcompat"), {
         exclude group: "androidx.appcompat", module: "appcompat"
         exclude group: "androidx.core", module: "core"
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesChangeWhenInBackground.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesChangeWhenInBackground.kt
index 140a237..047175d 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesChangeWhenInBackground.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesChangeWhenInBackground.kt
@@ -27,7 +27,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import junit.framework.TestCase.assertNotSame
 import org.junit.Before
 import org.junit.Rule
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesForegroundDialogTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesForegroundDialogTestCase.kt
index b43260e..f705ffb 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesForegroundDialogTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesForegroundDialogTestCase.kt
@@ -25,7 +25,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import junit.framework.TestCase.assertNotSame
 import org.junit.Before
 import org.junit.Ignore
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateTestCase.kt
index 6789203..996d45c 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateTestCase.kt
@@ -26,7 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt
index df74d85..6abd953 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt
@@ -28,7 +28,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.testutils.LifecycleOwnerUtils
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import org.junit.After
 import org.junit.Assert.assertNotSame
 import org.junit.Assert.assertSame
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt
index 74862a3..9b5f903 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt
@@ -30,7 +30,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.testutils.LifecycleOwnerUtils
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import org.junit.After
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertNotNull
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesStackedHandlingTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesStackedHandlingTestCase.kt
index e6dd610..23d0e79 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesStackedHandlingTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesStackedHandlingTestCase.kt
@@ -33,7 +33,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import junit.framework.Assert.assertNotNull
 import junit.framework.Assert.assertNotSame
 import org.junit.After
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeDefaultOnlyTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeDefaultOnlyTestCase.kt
index 3cc4620..5563c84 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeDefaultOnlyTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeDefaultOnlyTestCase.kt
@@ -27,7 +27,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import junit.framework.TestCase.assertNotSame
 import org.junit.Rule
 import org.junit.Test
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeForegroundDialogTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeForegroundDialogTestCase.kt
index 2ee588c..54c95bf 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeForegroundDialogTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeForegroundDialogTestCase.kt
@@ -25,7 +25,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import junit.framework.TestCase.assertNotSame
 import org.junit.Ignore
 import org.junit.Rule
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt
index 736520b..dca5cdc 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt
@@ -24,7 +24,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt
index 7c7254a..12e0102 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt
@@ -24,7 +24,7 @@
 import androidx.appcompat.testutils.NightModeUtils.setNightModeAndWaitForRecreate
 import androidx.lifecycle.Lifecycle
 import androidx.test.filters.LargeTest
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt
index c698bba..539d258 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt
@@ -30,7 +30,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.testutils.LifecycleOwnerUtils
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import org.junit.After
 import org.junit.Assert.assertNotSame
 import org.junit.Assert.assertSame
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt
index 962dfd5..e813d41 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt
@@ -32,7 +32,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.testutils.LifecycleOwnerUtils
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import org.junit.After
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertNotNull
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
index ceed8d6..a2a65e8 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
@@ -32,7 +32,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import junit.framework.Assert.assertNotNull
 import junit.framework.Assert.assertNotSame
 import org.junit.Test
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
index b14c6ae..7333865 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
@@ -38,7 +38,7 @@
 import androidx.test.espresso.matcher.ViewMatchers.withText
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils.waitForRecreation
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitForRecreation
 import androidx.testutils.waitForExecution
 import java.util.concurrent.CountDownLatch
 import org.junit.Assert.assertEquals
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt
index 953836e..3df5b6f 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt
@@ -24,7 +24,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.TimeUnit
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
index 52949dc..340b0b7 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
@@ -25,7 +25,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.TimeUnit
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt
index f72d0cf..3975a65 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt
@@ -29,7 +29,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils.waitUntilState
+import androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/LocalesUtils.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/LocalesUtils.kt
index bad3a2a..9fbdafb 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/LocalesUtils.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/LocalesUtils.kt
@@ -25,8 +25,8 @@
 import androidx.core.os.LocaleListCompat
 import androidx.lifecycle.Lifecycle
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils
 import androidx.testutils.PollingCheck
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import java.util.Locale
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
index 630a1d0..633827f 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
@@ -26,8 +26,8 @@
 import androidx.appcompat.app.AppCompatDelegate.NightMode
 import androidx.lifecycle.Lifecycle
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.testutils.LifecycleOwnerUtils
 import androidx.testutils.PollingCheck
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
 
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseViewTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseViewTest.java
index 83fa83c4..d87f8fb 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseViewTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseViewTest.java
@@ -25,7 +25,7 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.testutils.LifecycleOwnerUtils.waitUntilState;
+import static androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState;
 import static androidx.testutils.PollingCheck.waitFor;
 
 import static org.junit.Assert.assertNull;
diff --git a/camera/integration-tests/coretestapp/build.gradle b/camera/integration-tests/coretestapp/build.gradle
index 4dda00a..b514d3b 100644
--- a/camera/integration-tests/coretestapp/build.gradle
+++ b/camera/integration-tests/coretestapp/build.gradle
@@ -120,6 +120,7 @@
     androidTestImplementation(project(":concurrent:concurrent-futures"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
     androidTestImplementation(project(":internal-testutils-runtime"))
+    androidTestImplementation(project(":internal-testutils-lifecycle"))
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.3.1")
     androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
index 7bb399c..d65e176 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
@@ -51,7 +51,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
 import androidx.test.rule.GrantPermissionRule
-import androidx.testutils.LifecycleOwnerUtils
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CompletableDeferred
diff --git a/core/core/build.gradle b/core/core/build.gradle
index 5f369e5..9b69c43 100644
--- a/core/core/build.gradle
+++ b/core/core/build.gradle
@@ -59,6 +59,7 @@
     androidTestImplementation(project(":internal-testutils-runtime"), {
         exclude group: "androidx.core", module: "core"
     })
+    androidTestImplementation(project(":internal-testutils-lifecycle"))
     androidTestImplementation(project(":internal-testutils-fonts"))
     androidTestImplementation(project(":internal-testutils-mockito"))
 
diff --git a/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateFromLifecycleStatesTestCase.kt b/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateFromLifecycleStatesTestCase.kt
index 7c99d87..75585d3 100644
--- a/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateFromLifecycleStatesTestCase.kt
+++ b/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateFromLifecycleStatesTestCase.kt
@@ -22,8 +22,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.rule.ActivityTestRule
-import androidx.testutils.LifecycleOwnerUtils
 import androidx.testutils.PollingCheck
+import androidx.testutils.lifecycle.LifecycleOwnerUtils
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import junit.framework.Assert.assertEquals
diff --git a/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateTestCase.java b/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateTestCase.java
index 4a7f0f3..dfeb63c 100644
--- a/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateTestCase.java
+++ b/core/core/src/androidTest/java/androidx/core/app/ActivityCompatRecreateTestCase.java
@@ -16,7 +16,7 @@
 
 package androidx.core.app;
 
-import static androidx.testutils.LifecycleOwnerUtils.waitUntilState;
+import static androidx.testutils.lifecycle.LifecycleOwnerUtils.waitUntilState;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotSame;
diff --git a/lifecycle/lifecycle-runtime/build.gradle b/lifecycle/lifecycle-runtime/build.gradle
index 47432e7..cd82e0c 100644
--- a/lifecycle/lifecycle-runtime/build.gradle
+++ b/lifecycle/lifecycle-runtime/build.gradle
@@ -42,6 +42,7 @@
 
         commonTest {
             dependencies {
+                implementation(project(":internal-testutils-lifecycle"))
                 implementation(libs.kotlinCoroutinesTest)
                 implementation(libs.kotlinTest)
                 implementation(project(":kruth:kruth"))
@@ -61,6 +62,7 @@
         }
 
         desktopTest {
+            dependsOn(commonTest)
             dependencies {
                 implementation(libs.kotlinCoroutinesSwing)
             }
diff --git a/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/LaunchWhenTest.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/LaunchWhenTest.kt
index 332464f..c0df6da 100644
--- a/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/LaunchWhenTest.kt
+++ b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/LaunchWhenTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.testutils.lifecycle.FakeLifecycleOwner
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
diff --git a/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/PausingDispatcherTest.kt b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/PausingDispatcherTest.kt
index a45777d..1193f09 100644
--- a/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/PausingDispatcherTest.kt
+++ b/lifecycle/lifecycle-runtime/src/androidInstrumentedTest/kotlin/androidx/lifecycle/PausingDispatcherTest.kt
@@ -19,6 +19,7 @@
 import android.util.Log
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.testutils.lifecycle.FakeLifecycleOwner
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executors
diff --git a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/FlowWithLifecycleTest.kt b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/FlowWithLifecycleTest.kt
index 112eec4..c33fd1c 100644
--- a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/FlowWithLifecycleTest.kt
+++ b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/FlowWithLifecycleTest.kt
@@ -17,6 +17,7 @@
 package androidx.lifecycle
 
 import androidx.kruth.assertThat
+import androidx.testutils.lifecycle.FakeLifecycleOwner
 import kotlin.test.Test
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.coroutineScope
diff --git a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/RepeatOnLifecycleTest.kt b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/RepeatOnLifecycleTest.kt
index 08220d6..af3f9dc 100644
--- a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/RepeatOnLifecycleTest.kt
+++ b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/RepeatOnLifecycleTest.kt
@@ -17,6 +17,7 @@
 package androidx.lifecycle
 
 import androidx.kruth.assertThat
+import androidx.testutils.lifecycle.FakeLifecycleOwner
 import kotlin.test.Test
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineExceptionHandler
diff --git a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/WithLifecycleStateTest.kt b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/WithLifecycleStateTest.kt
index 7c7f760..039f2e5 100644
--- a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/WithLifecycleStateTest.kt
+++ b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/WithLifecycleStateTest.kt
@@ -17,6 +17,7 @@
 package androidx.lifecycle
 
 import androidx.kruth.assertWithMessage
+import androidx.testutils.lifecycle.FakeLifecycleOwner
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
diff --git a/settings.gradle b/settings.gradle
index 5f6211e..2c98097 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1133,6 +1133,7 @@
 includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.TOOLS])
 includeProject(":internal-testutils-mockito", "testutils/testutils-mockito", [BuildType.MAIN, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":internal-testutils-lifecycle", "testutils/testutils-lifecycle", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.KMP])
 includeProject(":kruth:kruth", [BuildType.MAIN, BuildType.INFRAROGUE, BuildType.KMP, BuildType.COMPOSE])
 
 /////////////////////////////
diff --git a/testutils/testutils-lifecycle/build.gradle b/testutils/testutils-lifecycle/build.gradle
new file mode 100644
index 0000000..1da51cc
--- /dev/null
+++ b/testutils/testutils-lifecycle/build.gradle
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+import androidx.build.LibraryType
+import androidx.build.PlatformIdentifier
+import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+}
+
+androidXMultiplatform {
+    android()
+    desktop()
+    mac()
+    linux()
+    ios()
+
+    kotlin {
+        explicitApi = ExplicitApiMode.Strict
+    }
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(projectOrArtifact(":lifecycle:lifecycle-runtime"))
+                api("androidx.annotation:annotation:1.8.0")
+
+                api(libs.kotlinStdlib)
+                api(libs.kotlinCoroutinesCore)
+                api(libs.kotlinCoroutinesTest)
+            }
+        }
+
+        androidMain {
+            dependsOn(commonMain)
+            dependencies {
+                api(libs.testRules)
+                implementation(libs.testExtJunit)
+                implementation(libs.testCore)
+            }
+        }
+    }
+}
+
+android {
+    namespace "androidx.testutils.lifecycle"
+}
+
+androidx {
+    type = LibraryType.INTERNAL_TEST_LIBRARY
+}
diff --git a/testutils/testutils-lifecycle/src/androidMain/kotlin/androidx/testutils/lifecycle/LifecycleOwnerUtils.android.kt b/testutils/testutils-lifecycle/src/androidMain/kotlin/androidx/testutils/lifecycle/LifecycleOwnerUtils.android.kt
new file mode 100644
index 0000000..abddfe6
--- /dev/null
+++ b/testutils/testutils-lifecycle/src/androidMain/kotlin/androidx/testutils/lifecycle/LifecycleOwnerUtils.android.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.testutils.lifecycle
+
+import android.app.Activity
+import android.app.Instrumentation.ActivityMonitor
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.hamcrest.CoreMatchers
+import org.hamcrest.MatcherAssert
+
+/** Utility methods for testing LifecycleOwners */
+public object LifecycleOwnerUtils {
+
+    private const val TIMEOUT_MS: Long = 5000
+
+    private val DO_NOTHING = Runnable {}
+
+    /**
+     * Waits until the given [LifecycleOwner] has the specified
+     * [androidx.lifecycle.Lifecycle.State]. If the owner has not hit that state within a suitable
+     * time period, it asserts that the current state equals the given state.
+     */
+    @JvmStatic
+    @Throws(Throwable::class)
+    public fun waitUntilState(owner: LifecycleOwner, state: Lifecycle.State) {
+        if (owner.lifecycle.currentState == state) {
+            return
+        }
+
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        val latch = CountDownLatch(1)
+
+        instrumentation.runOnMainSync {
+            if (owner.lifecycle.currentState == state) {
+                latch.countDown()
+                return@runOnMainSync
+            }
+
+            val observer =
+                object : LifecycleEventObserver {
+                    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                        if (source.lifecycle.currentState == state) {
+                            source.lifecycle.removeObserver(this)
+                            latch.countDown()
+                        }
+                    }
+                }
+
+            owner.lifecycle.addObserver(observer)
+        }
+
+        val isCountZero = latch.await(15, TimeUnit.SECONDS)
+        MatcherAssert.assertThat(
+            "Expected state $state never happened to $owner. " +
+                "Current state: ${owner.lifecycle.currentState}",
+            isCountZero,
+            CoreMatchers.`is`(true)
+        )
+
+        // wait for another loop to ensure all observers are called
+        instrumentation.runOnMainSync(DO_NOTHING)
+    }
+
+    /**
+     * Waits until the given the current [Activity] has been recreated, and the new instance is
+     * resumed.
+     */
+    @Throws(Throwable::class)
+    public fun <T> waitForRecreation(
+        @Suppress("deprecation") activityRule: androidx.test.rule.ActivityTestRule<T>
+    ): T where T : Activity, T : LifecycleOwner {
+        return waitForRecreation(activityRule.activity)
+    }
+
+    /**
+     * Waits until the given the given [Activity] has been recreated, and the new instance is
+     * resumed.
+     */
+    @Throws(Throwable::class)
+    public fun <T> waitForRecreation(activity: T): T where T : Activity, T : LifecycleOwner {
+        return waitForRecreation(activity, null)
+    }
+
+    /**
+     * Waits until the given [Activity] and [LifecycleOwner] has been recreated, and the new
+     * instance is resumed.
+     */
+    @Throws(Throwable::class)
+    public fun <T> waitForRecreation(activity: T, actionOnUiThread: Runnable?): T where
+    T : Activity,
+    T : LifecycleOwner {
+        val monitor = ActivityMonitor(activity::class.qualifiedName, null, false)
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        instrumentation.addMonitor(monitor)
+
+        if (actionOnUiThread != null) {
+            instrumentation.runOnMainSync(actionOnUiThread)
+        }
+
+        // Wait for the old activity to be destroyed. This helps avoid flakiness on test devices
+        // (ex. API 26) where the system takes a long time to go from STOPPED to DESTROYED.
+        waitUntilState(activity, Lifecycle.State.DESTROYED)
+
+        var recreatedActivity: T
+
+        // this guarantee that we will reinstall monitor between notifications about onDestroy
+        // and onCreate
+        // noinspection SynchronizationOnLocalVariableOrMethodParameter
+        try {
+            synchronized(monitor) {
+                do {
+                    // The documentation says "Block until an Activity is created
+                    // that matches this monitor." This statement is true, but there are some other
+                    // true statements like: "Block until an Activity is destroyed" or
+                    // "Block until an Activity is resumed"...
+                    // this call will release synchronization monitor's monitor
+                    @Suppress("UNCHECKED_CAST")
+                    recreatedActivity =
+                        monitor.waitForActivityWithTimeout(TIMEOUT_MS) as? T
+                            ?: throw RuntimeException("Timeout. Activity was not recreated.")
+                } while (recreatedActivity == activity)
+            }
+        } finally {
+            instrumentation.removeMonitor(monitor)
+        }
+
+        // Finally wait for the recreated Activity to be resumed
+        waitUntilState(recreatedActivity, Lifecycle.State.RESUMED)
+
+        return recreatedActivity
+    }
+}
diff --git a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/FakeLifecycleOwner.kt b/testutils/testutils-lifecycle/src/commonMain/kotlin/androidx/testutils/lifecycle/FakeLifecycleOwner.kt
similarity index 85%
rename from lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/FakeLifecycleOwner.kt
rename to testutils/testutils-lifecycle/src/commonMain/kotlin/androidx/testutils/lifecycle/FakeLifecycleOwner.kt
index 60ace27..bdbb587 100644
--- a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/FakeLifecycleOwner.kt
+++ b/testutils/testutils-lifecycle/src/commonMain/kotlin/androidx/testutils/lifecycle/FakeLifecycleOwner.kt
@@ -14,17 +14,22 @@
  * limitations under the License.
  */
 
-package androidx.lifecycle
+package androidx.testutils.lifecycle
 
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
 
 public class FakeLifecycleOwner(initialState: Lifecycle.State? = null) : LifecycleOwner {
-    private val registry: LifecycleRegistry = LifecycleRegistry.createUnsafe(this)
+
+    private val registry: LifecycleRegistry = LifecycleRegistry.createUnsafe(owner = this)
 
     init {
-        initialState?.let { setState(it) }
+        if (initialState != null) {
+            setState(initialState)
+        }
     }
 
     override val lifecycle: Lifecycle
@@ -53,8 +58,4 @@
     public fun resume() {
         runBlocking(Dispatchers.Main) { setState(Lifecycle.State.RESUMED) }
     }
-
-    private suspend fun getObserverCount(): Int {
-        return withContext(Dispatchers.Main) { registry.observerCount }
-    }
 }
diff --git a/testutils/testutils-runtime/src/main/java/androidx/testutils/LifecycleOwnerUtils.java b/testutils/testutils-runtime/src/main/java/androidx/testutils/LifecycleOwnerUtils.java
deleted file mode 100644
index 9b1b6d5..0000000
--- a/testutils/testutils-runtime/src/main/java/androidx/testutils/LifecycleOwnerUtils.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.testutils;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleEventObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utility methods for testing LifecycleOwners
- */
-public class LifecycleOwnerUtils {
-    private static final long TIMEOUT_MS = 5000;
-
-    private static final Runnable DO_NOTHING = new Runnable() {
-        @Override
-        public void run() {
-        }
-    };
-
-    /**
-     * Waits until the given {@link LifecycleOwner} has the specified
-     * {@link androidx.lifecycle.Lifecycle.State}. If the owner has not hit that state within a
-     * suitable time period, it asserts that the current state equals the given state.
-     */
-    public static void waitUntilState(final @NonNull LifecycleOwner owner,
-            final @NonNull Lifecycle.State state) throws Throwable {
-        final Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
-        if (currentState == state) {
-            return;
-        }
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                final Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
-                if (currentState == state) {
-                    latch.countDown();
-                    return;
-                }
-                owner.getLifecycle().addObserver(new LifecycleEventObserver() {
-                    @Override
-                    public void onStateChanged(@NonNull LifecycleOwner provider,
-                            @NonNull Lifecycle.Event event) {
-                        if (provider.getLifecycle().getCurrentState() == state) {
-                            provider.getLifecycle().removeObserver(this);
-                            latch.countDown();
-                        }
-                    }
-                });
-            }
-        });
-        final boolean latchResult = latch.await(15, TimeUnit.SECONDS);
-
-        assertThat("Expected " + state + " never happened to " + owner
-                        + ". Current state:" + owner.getLifecycle().getCurrentState(),
-                latchResult,
-                is(true));
-
-        // wait for another loop to ensure all observers are called
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(DO_NOTHING);
-    }
-
-    /**
-     * Waits until the given the current {@link Activity} has been recreated, and
-     * the new instance is resumed.
-     */
-    @NonNull
-    public static <T extends Activity & LifecycleOwner> T waitForRecreation(
-            @SuppressWarnings("deprecation")
-            @NonNull final androidx.test.rule.ActivityTestRule<T> activityRule
-    ) throws Throwable {
-        return waitForRecreation(activityRule.getActivity());
-    }
-
-    /**
-     * Waits until the given the given {@link Activity} has been recreated, and
-     * the new instance is resumed.
-     */
-    @NonNull
-    public static <T extends Activity & LifecycleOwner> T waitForRecreation(
-            @NonNull final T activity
-    ) throws Throwable {
-        return waitForRecreation(activity, null);
-    }
-
-    /**
-     * Waits until the given {@link Activity} and {@link LifecycleOwner} has been recreated, and
-     * the new instance is resumed.
-     */
-    @SuppressWarnings("unchecked")
-    @NonNull
-    public static <T extends Activity & LifecycleOwner> T waitForRecreation(
-            @NonNull final T activity,
-            @Nullable final Runnable actionOnUiThread
-    ) throws Throwable {
-        final Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
-                activity.getClass().getName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-
-        if (actionOnUiThread != null) {
-            instrumentation.runOnMainSync(actionOnUiThread);
-        }
-
-        // Wait for the old activity to be destroyed. This helps avoid flakiness on test devices
-        // (ex. API 26) where the system takes a long time to go from STOPPED to DESTROYED.
-        waitUntilState(activity, Lifecycle.State.DESTROYED);
-
-        T result;
-
-        // this guarantee that we will reinstall monitor between notifications about onDestroy
-        // and onCreate
-        // noinspection SynchronizationOnLocalVariableOrMethodParameter
-        try {
-            synchronized (monitor) {
-                do {
-                    // The documentation says "Block until an Activity is created
-                    // that matches this monitor." This statement is true, but there are some other
-                    // true statements like: "Block until an Activity is destroyed" or
-                    // "Block until an Activity is resumed"...
-                    // this call will release synchronization monitor's monitor
-                    result = (T) monitor.waitForActivityWithTimeout(TIMEOUT_MS);
-                    if (result == null) {
-                        throw new RuntimeException("Timeout. Activity was not recreated.");
-                    }
-                } while (result == activity);
-            }
-        } finally {
-            instrumentation.removeMonitor(monitor);
-        }
-
-        // Finally wait for the recreated Activity to be resumed
-        waitUntilState(result, Lifecycle.State.RESUMED);
-
-        return result;
-    }
-
-    private LifecycleOwnerUtils() {
-    }
-}